home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 4775 / 4775.xpi / chrome / autofillforms.jar / content / autofillForms.js next >
Text File  |  2008-08-25  |  162KB  |  5,052 lines

  1. /*
  2.  * @package autofillForms
  3.  * @author Sebastian Tschan
  4.  * @copyright (c) Sebastian Tschan
  5.  * @license GNU General Public License
  6.  * @link https://blueimp.net/mozilla/
  7.  */
  8.  
  9. var autofillForms = {
  10.     
  11.     // The selected profile index:
  12.     profileIndex: null,
  13.     // The selected global profile index:
  14.     globalProfileIndex: null,
  15.     // The selected form fields context menu profile index
  16.     formFieldsContextMenuProfileIndex: null,
  17.     // The list of profile labels:
  18.     profileLabels: null,
  19.     // The list of profile site rules:
  20.     profileSiteRules: null,
  21.     // The list of form field rules:
  22.     fieldRules: null,
  23.     // The tree representing the field rules list:
  24.     tree: null,
  25.     // The tree view:
  26.     treeView: null,
  27.     // The tree view frontend:
  28.     treeBox: null,
  29.     // Holds the selection object for the treeview:
  30.     selection: null,
  31.     // Remembers the last selected index of the treeview:
  32.     lastSelectedIndex: null,
  33.     // Determines if sort is to be ascending or descending:
  34.     ascending: null,
  35.     // The profiles listBox:
  36.     profilesTree: null,
  37.     // The profiles tree view:
  38.     profilesTreeView: null,
  39.     // The profiles tree view frontend:
  40.     profilesTreeBox: null,
  41.     // Holds the selection object for the profiles treeview:
  42.     profilesSelection: null,
  43.     // The profiles sort order:
  44.     profilesAscending: null,
  45.     // Autofill forms preferences branch:
  46.     autofillFormsPrefs: null,
  47.     // Object containing the shortcuts information (modifiers, key or keycode):
  48.     shortcut: null,
  49.     // Object containing the mouse button shortcuts information:
  50.     mouseButton: null,
  51.     // Helper var to do the opposite of the current setting:
  52.     invertedSetting: null,
  53.     // Array containing the rule element types ("begins with", "contains", ...):
  54.     ruleElementTypes: null,
  55.     // Containes the reference to the current rule field:
  56.     currentRuleField: null,
  57.     // Defines the index selected for the last alternative fieldRules selection:
  58.     fieldRuleAlternativesIndex: null,
  59.     // Stores the length of the last created list of alternative fieldRules:
  60.     fieldRuleAlternativesLength: null,
  61.     // Hash to store lists of alternatives (used for radio input fields):
  62.     fieldRuleAlternativesHash: null,
  63.     // Cache to reuse/clone fieldRuleAlternatives on the same form fill run:
  64.     fieldRuleAlternativesCache: null,
  65.     // Array of dynamic tags:
  66.     dynamicTags: null,
  67.     // Array of dynamic tag codes, associated to the dynamic tags:
  68.     dynamicTagCodes: null,
  69.     // Determines if a textbox is focused on the rule editor:
  70.     ruleEditorTextBoxFocused: null,
  71.     // Determines if a textbox is focused on the tag editor:
  72.     tagEditorTextBoxFocused: null,
  73.     // References the last matched form element (used to set the focus):
  74.     lastFormElementMatch: null,
  75.     // References the current window when filling out forms:
  76.     currentWindow: null,
  77.     // Holds the index of the current form when filling out forms:
  78.     currentFormIndex:null,
  79.     // Holds the index of the current form element when filling out forms:
  80.     currentElementIndex: null,
  81.     // References the target form field on which the context menu has been invoked:
  82.     targetFormField: null,
  83.     // Event listener for the content area context menu:
  84.     contentAreaContextMenuEventListener: null,
  85.     // Holds the the tooltip grid which displays commands and their mouse buttons and keyboard shortcuts:
  86.     tooltipGrid: null,
  87.     // Holds the current profile tooltip label:
  88.     tooltipCurrentProfile: null,
  89.     
  90.     initialize: function() {
  91.         // Save the reference to the Autofill Forms preferences branch:
  92.         this.autofillFormsPrefs = this.getPrefManager().getBranch('extensions.autofillForms@blueimp.net.');
  93.         
  94.         // Add a preferences observer to the autofillForms preferences branch:
  95.         this.autofillFormsPrefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
  96.          this.autofillFormsPrefs.addObserver('', this, false);
  97.  
  98.         // Implement the event listener for the content area context menu:
  99.         this.contentAreaContextMenuEventListener = function(event) {
  100.             autofillForms.initContentAreaContextMenu(event);
  101.         }
  102.          
  103.         // Initialize the preferences settings:
  104.         this.initializePrefs();
  105.     },
  106.     
  107.     initContentAreaContextMenu: function(event) {    
  108.         var cm0 = document.getElementById('autofillFormsContextMenuItem');
  109.         var cm1 = document.getElementById('autofillFormsContextMenu');
  110.         var cm2 = document.getElementById('autofillFormsManualFillContextMenu');
  111.         var cm3 = document.getElementById('autofillFormsAddRuleContextMenuItem');
  112.         var cm4 = document.getElementById('autofillFormsAddFormAsProfileContextMenuItem');
  113.         var cm5 = document.getElementById('autofillFormsContextMenuSeparator1');
  114.         var cm6 = document.getElementById('autofillFormsContextMenuSeparator2');
  115.         var cm7 = document.getElementById('autofillFormsDisplayFormDetailsContextMenuItem');
  116.         if(cm0 && gContextMenu) {
  117.             if(gContextMenu.target && this.isValidFormField(gContextMenu.target)) {
  118.                 cm0.hidden = true;
  119.                 cm1.hidden = true;
  120.                 if(this.autofillFormsPrefs.getBoolPref('hideFormFieldsContextMenu')) {
  121.                     cm2.hidden = true;
  122.                     cm3.hidden = true;
  123.                     cm4.hidden = true;
  124.                     cm5.hidden = true;
  125.                     cm6.hidden = true;
  126.                     cm7.hidden = true;
  127.                     this.targetFormField = null;
  128.                 } else {
  129.                     cm2.hidden = false;
  130.                     cm3.hidden = false;
  131.                     cm4.hidden = false;
  132.                     // Show menuseparators if not already separated:
  133.                     if(this.isPreviousNodeSeparated(cm5)) {
  134.                         cm5.hidden = true;
  135.                     } else {
  136.                         cm5.hidden = false;
  137.                     }
  138.                     if(this.isNextNodeSeparated(cm6)) {
  139.                         cm6.hidden = true;
  140.                     } else {
  141.                         cm6.hidden = false;
  142.                     }
  143.                     this.targetFormField = gContextMenu.target;
  144.                 }
  145.                 return;
  146.             }
  147.  
  148.             if(this.autofillFormsPrefs.getBoolPref('hideContextMenuItem')
  149.                 || gContextMenu.isContentSelected
  150.                 || gContextMenu.onTextInput
  151.                 || gContextMenu.onImage
  152.                 || gContextMenu.onLink
  153.                 || gContextMenu.onCanvas
  154.                 || gContextMenu.onMathML
  155.                 || !this.getDoc().forms
  156.                 || !this.getDoc().forms.length) {
  157.                 cm0.hidden = true;
  158.                 cm1.hidden = true;
  159.                 cm5.hidden = true;
  160.                 cm6.hidden = true;
  161.                 cm7.hidden = true;
  162.             } else {
  163.                 if(this.getProfileLabels().length == 1) {
  164.                     cm0.hidden = false;
  165.                     cm1.hidden = true;                    
  166.                 } else {
  167.                     cm0.hidden = true;
  168.                     cm1.hidden = false;
  169.                 }
  170.                 // Show menuseparators if not already separated:
  171.                 if(this.isPreviousNodeSeparated(cm5)) {
  172.                     cm5.hidden = true;
  173.                 } else {
  174.                     cm5.hidden = false;
  175.                 }
  176.                 if(this.isNextNodeSeparated(cm6)) {
  177.                     cm6.hidden = true;
  178.                 } else {
  179.                     cm6.hidden = false;
  180.                 }
  181.                 cm7.hidden = false;
  182.             }
  183.             cm2.hidden = true;
  184.             cm3.hidden = true;
  185.             cm4.hidden = true;
  186.             this.targetFormField = null;
  187.         }
  188.     },
  189.     
  190.     isNextNodeSeparated: function(node) {
  191.         while(node) {
  192.             node = node.nextSibling
  193.             if(node.hidden) {
  194.                 continue;
  195.             }
  196.             if(node.nodeName == 'menuseparator') {
  197.                 return true;
  198.             } else {
  199.                 return false;
  200.             }
  201.         }
  202.         return true;
  203.     },
  204.     
  205.     isPreviousNodeSeparated: function(node) {
  206.         while(node) {
  207.             node = node.previousSibling;
  208.             if(node.hidden) {
  209.                 continue;
  210.             }
  211.             if(node.nodeName == 'menuseparator') {
  212.                 return true;
  213.             } else {
  214.                 return false;
  215.             }
  216.         }
  217.         return true;
  218.     },
  219.     
  220.     initializePrefs: function() {
  221.         // Initialize the keyboard shortcut object container:
  222.         this.shortcut = new Object();
  223.         this.shortcut['shortcut'] = null;
  224.         this.shortcut['shortcutSubmit'] = null;
  225.         this.shortcut['shortcutAllTabs'] = null;
  226.         this.shortcut['shortcutFromProfileSelection'] = null;
  227.         this.shortcut['shortcutProfile'] = null;
  228.         this.shortcut['shortcutSettings'] = null;
  229.         this.shortcut['shortcutDisplayFormDetails'] = null;
  230.         for(var property in this.shortcut) {
  231.             this.updateShortcut(property);
  232.         }
  233.         // Initialize toolbar and statusbar icons and context menu:
  234.         this.hideToolbarButtonUpdate();
  235.         this.hideToolbarButtonMenuUpdate();
  236.         this.hideStatusbarIconUpdate();
  237.         this.hideContextMenuItemUpdate();
  238.     },
  239.  
  240.     observe: function(subject, topic, data) {
  241.         // Only observe preferences changes:
  242.         if (topic != 'nsPref:changed')
  243.             return;
  244.         switch(data) {
  245.             case 'profileIndex':
  246.                 // If set to null, the profileIndex will be updated on next getProfileIndex() call:
  247.                 this.profileIndex = null;
  248.                 this.tooltipCurrentProfile = null;
  249.                 break;
  250.             case 'globalProfileIndex':
  251.                 // If set to null, the globalProfileIndex will be updated on next getGlobalProfileIndex() call:
  252.                 this.globalProfileIndex = null;
  253.                 break;
  254.             case 'formFieldsContextMenuProfileIndex':
  255.                 // If set to null, the formFieldsContextMenuProfileIndex will be updated on next getFormFieldsContextMenuProfileIndex() call:
  256.                 this.formFieldsContextMenuProfileIndex = null;
  257.                 break;
  258.             case 'profileLabels':
  259.                 // If set to null, the profileLabels will be updated on next getProfileLabels() call:
  260.                 this.profileLabels = null;
  261.                 this.tooltipCurrentProfile = null;
  262.                 break;
  263.             case 'profileSiteRules':
  264.                 // If set to null, the profileSiteRules will be updated on next getProfileSiteRules() call:
  265.                 this.profileSiteRules = null;
  266.                 break;
  267.             case 'shortcut':
  268.                 this.updateShortcut('shortcut');
  269.                 this.tooltipGrid = null;
  270.                 break;
  271.             case 'shortcutSubmit':
  272.                 this.updateShortcut('shortcutSubmit');
  273.                 this.tooltipGrid = null;
  274.                 break;
  275.             case 'shortcutAllTabs':
  276.                 this.updateShortcut('shortcutAllTabs');
  277.                 this.tooltipGrid = null;
  278.                 break;
  279.             case 'shortcutFromProfileSelection':
  280.                 this.updateShortcut('shortcutFromProfileSelection');
  281.                 this.tooltipGrid = null;
  282.                 break;
  283.             case 'shortcutProfile':
  284.                 this.updateShortcut('shortcutProfile');
  285.                 this.tooltipGrid = null;
  286.                 break;
  287.             case 'shortcutSettings':
  288.                 this.updateShortcut('shortcutSettings');
  289.                 this.tooltipGrid = null;
  290.                 break;
  291.             case 'shortcutDisplayFormDetails':
  292.                 this.updateShortcut('shortcutDisplayFormDetails');
  293.                 this.tooltipGrid = null;
  294.                 break;
  295.             case 'mouseShortcut':
  296.                 if(this.mouseButton) {
  297.                     this.mouseButton['mouseShortcut'] = null;
  298.                     this.tooltipGrid = null;
  299.                 }
  300.                 break;
  301.             case 'mouseShortcutSubmit':
  302.                 if(this.mouseButton) {
  303.                     this.mouseButton['mouseShortcutSubmit'] = null;
  304.                     this.tooltipGrid = null;
  305.                 }
  306.                 break;
  307.             case 'mouseShortcutAllTabs':
  308.                 if(this.mouseButton) {
  309.                     this.mouseButton['mouseShortcutAllTabs'] = null;
  310.                     this.tooltipGrid = null;
  311.                 }
  312.                 break;
  313.             case 'mouseShortcutFromProfileSelection':
  314.                 if(this.mouseButton) {
  315.                     this.mouseButton['mouseShortcutFromProfileSelection'] = null;
  316.                     this.tooltipGrid = null;
  317.                 }
  318.                 break;
  319.             case 'mouseShortcutProfile':
  320.                 if(this.mouseButton) {
  321.                     this.mouseButton['mouseShortcutProfile'] = null;
  322.                     this.tooltipGrid = null;
  323.                 }
  324.                 break;
  325.             case 'mouseShortcutSettings':
  326.                 if(this.mouseButton) {
  327.                     this.mouseButton['mouseShortcutSettings'] = null;
  328.                     this.tooltipGrid = null;
  329.                 }
  330.                 break;
  331.             case 'mouseShortcutDisplayFormDetails':
  332.                 if(this.mouseButton) {
  333.                     this.mouseButton['mouseShortcutDisplayFormDetails'] = null;
  334.                     this.tooltipGrid = null;
  335.                 }
  336.                 break;
  337.             case 'fieldRules':
  338.                 if(!this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  339.                     // If set to null, the fieldRules will be updated on next getFieldRules() call:
  340.                     this.fieldRules = null;
  341.                 }
  342.                 break;
  343.             case 'storeEncrypted':
  344.                 // To update the stored data, we must decrypt or may not decrypt
  345.                 // the prefString in opposition to the setting which just changed -
  346.                 // the "invertedSetting" helper var helps to identify this situation:
  347.                 this.invertedSetting = true;
  348.                 // Store data encrypted/decrypted:
  349.                 this.setFieldRules();
  350.                 this.invertedSetting = false;
  351.                 break;
  352.             case 'dynamicTags':
  353.                 // If set to null, the dynamicTags will be updated on next getDynamicTags() call:
  354.                 this.dynamicTags = null;
  355.                 break;
  356.             case 'dynamicTagCodes':
  357.                 // If set to null, the dynamicTagCodes will be updated on next getDynamicTagCodes() call:
  358.                 this.dynamicTagCodes = null;
  359.                 break;
  360.             case 'hideContextMenuItem':
  361.                 this.hideContextMenuItemUpdate();
  362.                 break;
  363.             case 'hideFormFieldsContextMenu':
  364.                 this.hideContextMenuItemUpdate();
  365.                 break;
  366.             case 'hideStatusbarIcon':
  367.                 this.hideStatusbarIconUpdate();
  368.                 break;
  369.             case 'hideToolbarButton':
  370.                 this.hideToolbarButtonUpdate();
  371.                 this.hideToolbarButtonMenuUpdate();
  372.                 break;
  373.             case 'hideToolbarButtonMenu':
  374.                 this.hideToolbarButtonMenuUpdate();
  375.                 break;
  376.             case 'useConfigDirectory':
  377.                 if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  378.                     this.exportToConfigDirectory();
  379.                 } else {
  380.                     this.importFromConfigDirectory();
  381.                 }
  382.                 break;
  383.         }
  384.     },
  385.     
  386.     hideContextMenuItemUpdate: function() {
  387.         var contentAreaContextMenu = document.getElementById('contentAreaContextMenu');
  388.         if(contentAreaContextMenu) {
  389.             if(!this.autofillFormsPrefs.getBoolPref('hideContextMenuItem') 
  390.                 || !this.autofillFormsPrefs.getBoolPref('hideFormFieldsContextMenu')) {
  391.                 // Add the content area context menu listener:
  392.                 contentAreaContextMenu.addEventListener(
  393.                     'popupshowing',
  394.                     this.contentAreaContextMenuEventListener,
  395.                     false
  396.                 );
  397.             } else {
  398.                 var cm0 = document.getElementById('autofillFormsContextMenuItem');
  399.                 var cm1 = document.getElementById('autofillFormsContextMenu');
  400.                 var cm2 = document.getElementById('autofillFormsManualFillContextMenu');
  401.                 var cm3 = document.getElementById('autofillFormsAddRuleContextMenuItem');
  402.                 var cm4 = document.getElementById('autofillFormsAddFormAsProfileContextMenuItem');
  403.                 var cm5 = document.getElementById('autofillFormsContextMenuSeparator1');
  404.                 var cm6 = document.getElementById('autofillFormsContextMenuSeparator2');
  405.                 if(cm0) {
  406.                     cm0.hidden = true;
  407.                     cm1.hidden = true;
  408.                     cm2.hidden = true;
  409.                     cm3.hidden = true;
  410.                     cm4.hidden = true;
  411.                     cm5.hidden = true;
  412.                     cm6.hidden = true;
  413.                 }
  414.                 // Remove the content area context menu listener:
  415.                 this.targetFormField = null;
  416.                 contentAreaContextMenu.removeEventListener(
  417.                     'popupshowing',
  418.                     this.contentAreaContextMenuEventListener,
  419.                     false
  420.                 );
  421.             }
  422.         }
  423.     },
  424.     
  425.     hideStatusbarIconUpdate: function() {
  426.         // Change the statusbar icon visibility:
  427.         var autofillFormsPanelIcon = document.getElementById('autofillFormsPanelIcon');
  428.         if(autofillFormsPanelIcon) {
  429.             autofillFormsPanelIcon.setAttribute(
  430.                 'hidden',
  431.                 this.autofillFormsPrefs.getBoolPref('hideStatusbarIcon')
  432.             );
  433.         }
  434.     },
  435.  
  436.     installToolbarButton: function(buttonID, beforeNodeID, toolbarID) {
  437.         beforeNodeID = beforeNodeID ? beforeNodeID : 'urlbar-container';
  438.         toolbarID = toolbarID ? toolbarID : 'navigation-toolbar';
  439.         if(!document.getElementById(buttonID)) {
  440.             var toolbar = document.getElementById(toolbarID);
  441.             if(!toolbar) {
  442.                 // Firefox < 3:
  443.                 toolbar = document.getElementById('nav-bar');
  444.             }            
  445.             if(toolbar && 'insertItem' in toolbar) {
  446.                 var beforeNode = document.getElementById(beforeNodeID);
  447.                 if(beforeNode && beforeNode.parentNode != toolbar) {
  448.                     beforeNode = null;
  449.                 }
  450.                 // Insert before the given node or at the end of the toolbar if the node is not available:
  451.                 toolbar.insertItem(buttonID, beforeNode, null, false);
  452.                 toolbar.setAttribute('currentset', toolbar.currentSet);
  453.                 document.persist(toolbar.id, 'currentset');
  454.             }
  455.         }
  456.     },
  457.  
  458.     hideToolbarButtonUpdate: function() {
  459.         var autofillFormsButton = document.getElementById('autofillFormsButton');
  460.         var hideToolbarButton = this.autofillFormsPrefs.getBoolPref('hideToolbarButton');
  461.         if(!autofillFormsButton && !hideToolbarButton) {
  462.             // Add the toolbar button to the toolbar:
  463.             this.installToolbarButton('autofillFormsButton');
  464.             autofillFormsButton = document.getElementById('autofillFormsButton');
  465.         }
  466.         if(autofillFormsButton) {
  467.             autofillFormsButton.setAttribute(
  468.                 'hidden',
  469.                 hideToolbarButton
  470.             );
  471.         }
  472.     },
  473.     
  474.     hideToolbarButtonMenuUpdate: function() {
  475.         var autofillFormsButton = document.getElementById('autofillFormsButton');
  476.         if(autofillFormsButton) {
  477.             if(this.autofillFormsPrefs.getBoolPref('hideToolbarButtonMenu')) {
  478.                 autofillFormsButton.removeAttribute('type');
  479.             } else {
  480.                 autofillFormsButton.setAttribute('type','menu-button');
  481.             }
  482.         }
  483.     },
  484.  
  485.     commandHandler: function(event) {
  486.         if(typeof event.button == 'undefined') {
  487.             // If no event.button is set, the command has been done by the left mouse button:
  488.             event.button = 0;
  489.         }
  490.         // Recognize the mouse button and perform the associated action:
  491.         var mouseButtonObj = this.recognizeMouseButton(event);
  492.         if(this.getMouseButton('mouseShortcut').equals(mouseButtonObj)) {
  493.             this.fillForms();
  494.         } else if(this.getMouseButton('mouseShortcutSubmit').equals(mouseButtonObj)) {
  495.             this.fillForms(null, null, true);
  496.         } else if(this.getMouseButton('mouseShortcutAllTabs').equals(mouseButtonObj)) {
  497.             this.fillForms(null, null, null, true);
  498.         } else if(this.getMouseButton('mouseShortcutFromProfileSelection').equals(mouseButtonObj)) {
  499.             this.profileSelectionFormFillPopup(event);
  500.         } else if(this.getMouseButton('mouseShortcutProfile').equals(mouseButtonObj)) {
  501.             this.showProfileSwitcher(event);
  502.         } else if(this.getMouseButton('mouseShortcutSettings').equals(mouseButtonObj)) {
  503.             this.showDialog('chrome://autofillForms/content/autofillFormsOptions.xul');
  504.         } else if(this.getMouseButton('mouseShortcutDisplayFormDetails').equals(mouseButtonObj)) {
  505.             this.displayFormDetails();
  506.         }
  507.     },
  508.     
  509.     clickHandler: function(event) {
  510.         switch(event.button) {
  511.             case 0:
  512.                 // The left mouse button is already handled for clicks on the toolbar button,
  513.                 // but not for clicks on the status bar icon:
  514.                 if(event.target.id == 'autofillFormsPanelIcon') {
  515.                     this.commandHandler(event);
  516.                 }
  517.                 break;
  518.             default:
  519.                 this.commandHandler(event);
  520.         }
  521.     },
  522.     
  523.     profileSelectionFormFillPopup: function(event) {
  524.         var popup = document.getElementById('autofillFormsProfileSelectionPopup');        
  525.         if(popup && typeof popup.openPopup == 'function') {
  526.             this.prepareProfileSelectionFormFillMenu(popup);
  527.             // Show the popup menu (only available for Firefox >= 3):
  528.             popup.openPopup(event.target, null, 0, 0, false, true);
  529.         } else {
  530.             this.profileSelectionFormFillPrompt(event);
  531.         }
  532.     },
  533.     
  534.     prepareProfileSelectionFormFillMenu: function(menupopup) {
  535.         // Remove all children nodes:
  536.         while(menupopup.hasChildNodes()) {
  537.             menupopup.removeChild(menupopup.firstChild);
  538.         }
  539.         var menuitem = document.createElement('menuitem');
  540.         menuitem.setAttribute('class','menuitem-iconic autofillFormsIcon');
  541.         // Add the profile labels as menu items:
  542.         for(var i=0; i < this.getProfileLabels().length; i++) {
  543.             menuitem = menuitem.cloneNode(false);
  544.             menuitem.setAttribute('label', this.getProfileLabel(i));
  545.             menuitem.setAttribute('oncommand', 'autofillForms.fillForms(null,'+i+');');
  546.             menupopup.appendChild(menuitem);
  547.         }
  548.     },
  549.     
  550.     profileSelectionFormFillPrompt: function(event) {
  551.         // Show a profile selection prompt and fill out forms with the selected profile:
  552.         var list = this.getProfileLabels();
  553.         var selected = {};
  554.         var ok = this.getPrompts().select(
  555.             window,
  556.             this.getStringBundle().getString('profileSelectionFormFillTitle'),
  557.             this.getStringBundle().getString('profileSelectionPrompt'),
  558.             list.length,
  559.             list,
  560.             selected
  561.         );
  562.         if(ok) {
  563.             this.fillForms(null, selected.value);
  564.         }
  565.     },
  566.     
  567.     fillForms: function(win, profileIndex, autoSubmit, allTabs) {
  568.         if(!win || !win.document) {
  569.             win = this.getWin();
  570.         }
  571.         
  572.         var currentProfileIndex = this.getProfileIndex();
  573.         var toggleAutoSelectBestProfile;
  574.         if(typeof profileIndex == 'number') {
  575.             // Temporarily set the given profile index:
  576.             this.setProfileIndex(profileIndex);
  577.             
  578.             if(this.autofillFormsPrefs.getBoolPref('autoSelectBestProfile')) {
  579.                 // Temporarily disable autoSelectBestProfile:
  580.                 this.autofillFormsPrefs.setBoolPref('autoSelectBestProfile', false);
  581.                 toggleAutoSelectBestProfile = true;
  582.             }
  583.         }
  584.         
  585.         autoSubmit = autoSubmit ? autoSubmit : null;
  586.         
  587.         if(allTabs) {
  588.             // Fill out forms on all open browser tabs:
  589.             for(var i=0; i<this.getBrowser().browsers.length; i++) {
  590.                 this.searchAndFillForms(
  591.                     this.getBrowser().getBrowserAtIndex(i).contentWindow,
  592.                     autoSubmit
  593.                 );
  594.             }
  595.         } else {
  596.             // Fill out forms on the current tab (or the given window object):
  597.             this.searchAndFillForms(win, autoSubmit);    
  598.         }
  599.  
  600.         // Reset Alternatives (including the cache):
  601.         this.fieldRuleAlternativesIndex = null;
  602.         this.fieldRuleAlternativesLength = null;
  603.         this.fieldRuleAlternativesHash = null;
  604.         this.fieldRuleAlternativesCache = null;
  605.         
  606.         // Reset objects to release used memory:
  607.         this.fieldRules = null;
  608.         this.profileSiteRules = null;
  609.         this.dynamicTags = null;
  610.         this.dynamicTagCodes = null;
  611.         
  612.         // Reset the selected profile:
  613.         this.setProfileIndex(currentProfileIndex);
  614.         
  615.         if(toggleAutoSelectBestProfile) {
  616.             // Reenable autoSelectBestProfile:
  617.             this.autofillFormsPrefs.setBoolPref('autoSelectBestProfile', true);
  618.         }
  619.     },
  620.     
  621.     searchAndFillForms: function(win, autoSubmit) {
  622.         var doc = this.getDoc(win);
  623.         
  624.         // Check if any web forms are available on the current window:
  625.         if(doc && doc.forms && doc.forms.length > 0) {
  626.  
  627.             var url = doc.location.href;
  628.  
  629.             if(this.autofillFormsPrefs.getBoolPref('autoSelectBestProfile')) {
  630.                 // Remember the currently selected profile:
  631.                 var currentProfileIndex = this.getProfileIndex();
  632.             }
  633.             
  634.             // Select the best matching profile - returns false if none matches:
  635.             if(!this.selectBestMatchingProfile(url)) {
  636.                 return;
  637.             }
  638.  
  639.             this.currentWindow = win;
  640.  
  641.             // Holds the form to be submitted:
  642.             var submitForm;
  643.             // Holds the first submit element found on the form:
  644.             var submitElement;
  645.             
  646.              // Go through the forms:
  647.              for(var i = 0; i < doc.forms.length; i++) {
  648.                 this.currentFormIndex = i;
  649.                  
  650.                  // The form elements list:
  651.                 var elements = doc.forms[i].elements;
  652.                 
  653.                 // A hash to store the alternatives for radio input fields:
  654.                 this.fieldRuleAlternativesHash = new Object();
  655.                 
  656.                 // Go through the form elements:
  657.                 for(var j = 0; j < elements.length; j++) {                    
  658.                     this.currentElementIndex = j;
  659.                     
  660.                     // Fill out valid form field types:
  661.                     if(this.isValidFormField(elements[j])) {
  662.                         this.setFormField(elements[j], url);
  663.                     }
  664.                     
  665.                     // Collect the first submit button of the form if autoSubmit is enabled:
  666.                     if(autoSubmit && elements[j].type && elements[j].type == 'submit' && !submitElement) {
  667.                         submitElement = elements[j];
  668.                     }
  669.                 }
  670.                 
  671.                 this.applyStoredFieldRulesAlternatives();
  672.                 
  673.                 if(autoSubmit) {
  674.                     if(this.lastFormElementMatch && this.lastFormElementMatch.form == doc.forms[i]) {
  675.                         // Elements have been matched on this form, check the submitElement:
  676.                         if(!submitElement) {
  677.                             submitElement = this.getImageSubmitButton(doc.forms[i]);
  678.                         }
  679.                         submitForm = doc.forms[i];
  680.                         // Break out of the forms loop:
  681.                         break;
  682.                     } else {
  683.                         submitElement = null;
  684.                     }
  685.                 }
  686.             }
  687.             
  688.             if(this.autofillFormsPrefs.getBoolPref('autoSelectBestProfile')) {
  689.                 // Reset the selected profile to the manually selected one:
  690.                 this.setProfileIndex(currentProfileIndex);
  691.             }
  692.  
  693.             if(this.lastFormElementMatch && this.autofillFormsPrefs.getBoolPref('focusLastFormElementMatch')) {
  694.                 // Set the focus to the last matched form element:
  695.                 this.lastFormElementMatch.focus();
  696.             }
  697.  
  698.             // Reset the last matched form element:
  699.             this.lastFormElementMatch = null;
  700.             
  701.             this.currentWindow = null;
  702.             this.currentFormIndex = null;
  703.             this.currentElementIndex = null;
  704.             
  705.             if(autoSubmit && submitForm) {
  706.                 // autoSubmit the form with a click on the submit button if found
  707.                 // or else by calling the submit() method on the form:
  708.                 if(submitElement) {
  709.                     submitElement.click();
  710.                 } else {
  711.                     submitForm.submit();
  712.                 }
  713.             }
  714.         }
  715.  
  716.         // Recursive call for all subframes:
  717.         for(var f=0; f < win.frames.length; f++) {
  718.             this.searchAndFillForms(win.frames[f], autoSubmit);
  719.         }
  720.     },
  721.     
  722.     getImageSubmitButton: function(form) {
  723.          var inputElements = form.getElementsByTagName('input');
  724.          for(var i = 0; i < inputElements.length; i++) {
  725.             if(inputElements[i].type == 'image') {
  726.                 return inputElements[i];
  727.             }
  728.          }
  729.     },
  730.  
  731.     selectBestMatchingProfile: function(url) {
  732.         if(this.autofillFormsPrefs.getBoolPref('autoSelectBestProfile')) {
  733.             var match;
  734.             // The emtpy siteRule (?:) has a match length of 0, so we set the initial value to -1:
  735.             var maxMatch = -1;
  736.             var index = -1;
  737.             // First test the currently selected profile:
  738.             try {
  739.                 match = url.match(new RegExp(this.getProfileSiteRule(this.getProfileIndex()),'i'));
  740.                 if(match && (match.toString()).length > maxMatch) {
  741.                     maxMatch = (match.toString()).length;
  742.                     index = this.getProfileIndex();
  743.                 }
  744.             } catch(e) {
  745.                 // Catch errors caused by invalid profile site rules
  746.             }    
  747.             for(var i=0; i<this.getProfileSiteRules().length; i++) {
  748.                 if(i == this.getProfileIndex()) {
  749.                     // Skip the current profile (already tested):
  750.                     continue;
  751.                 }
  752.                 try {
  753.                     match = url.match(new RegExp(this.getProfileSiteRule(i),'i'));
  754.                     if(match && (match.toString()).length > maxMatch) {
  755.                         maxMatch = (match.toString()).length;
  756.                         index = i;
  757.                     }
  758.                 } catch(e) {
  759.                     // Catch errors caused by invalid profile site rules
  760.                 }
  761.             }
  762.             if(index > -1) {
  763.                 // Select the profile with the best match:
  764.                 this.setProfileIndex(index);
  765.                 return true;
  766.             }
  767.         } else {
  768.             try {
  769.                 var regExp = new RegExp(this.getProfileSiteRule(this.getProfileIndex()),'i');
  770.                 if(regExp.test(url)) {
  771.                     return true;
  772.                 }
  773.             } catch(e) {
  774.                 // Catch errors caused by invalid profile site rules
  775.             }
  776.         }
  777.         return false;
  778.     },
  779.     
  780.     setFormField: function(element,url) {
  781.         var matchFound = false;
  782.         
  783.         // Apply the fieldRules of the current profile:
  784.         matchFound = this.applyFieldRulesOnElement(element,url,this.getFieldRules());
  785.         
  786.         // If no match has been found, apply the fieldRules of the global profile, if enabled:
  787.         if(!matchFound && this.autofillFormsPrefs.getBoolPref('enableGlobalProfile')) {
  788.             // Only apply the global profile fieldRules if the current profile is not the global profile;
  789.             if(this.getProfileIndex() != this.getGlobalProfileIndex()) {
  790.                 // Only apply the global profile if the global profile site rule matches the url:
  791.                 try {
  792.                     var regExp = new RegExp(this.getProfileSiteRule(this.getGlobalProfileIndex()),'i');
  793.                     if(regExp.test(url)) {
  794.                         matchFound = this.applyFieldRulesOnElement(element,url,this.getGlobalFieldRules());
  795.                     }
  796.                 } catch(e) {
  797.                     // Catch errors caused by invalid profile site rules
  798.                 }
  799.             }
  800.         }
  801.  
  802.         // Highlight styles:
  803.         var highlightStyleMatch = this.autofillFormsPrefs.getCharPref('highlightStyleMatch');
  804.         var highlightStyleNoMatch = this.autofillFormsPrefs.getCharPref('highlightStyleNoMatch');
  805.  
  806.         if(matchFound) {
  807.             // Set the current element as the last matched form element:
  808.             this.lastFormElementMatch = element;
  809.             
  810.             if(highlightStyleMatch) {
  811.                 // Highlight matched form fieds:
  812.                 element.setAttribute('style', highlightStyleMatch);    
  813.             }
  814.         } else if(highlightStyleNoMatch) {
  815.             // Highlight not matched form fieds:
  816.             element.setAttribute('style', highlightStyleNoMatch);
  817.         }
  818.     },
  819.  
  820.     getIndexForFieldRules: function(fieldRules) {
  821.         if(this.fieldRules) {
  822.             for(var i=0; i<this.fieldRules.length; i++) {
  823.                 if(this.fieldRules[i] === fieldRules) {
  824.                     return i;
  825.                 }
  826.             }
  827.         }
  828.         return -1;
  829.     },
  830.     
  831.     fieldRuleAlternativeFactory: function(fieldRules, index) {
  832.         var af = this;
  833.         if(typeof arguments.callee.fieldRuleAlternative == 'undefined') {
  834.             arguments.callee.fieldRuleAlternative = function(fieldRules, index) {
  835.                 this.fieldRules = fieldRules;
  836.                 this.index = index;
  837.                 this.fieldRule = this.fieldRules[this.index];
  838.                 return this;
  839.             }
  840.             arguments.callee.fieldRuleAlternative.prototype = {
  841.                 af : af,
  842.                 fieldRuleValue: null,
  843.                 fieldRuleValueRegExp: null,
  844.                 fieldRuleRegExp: null,
  845.                 siteRuleRegExp: null,
  846.                 optionsIndex: null,
  847.                 element: null,
  848.                 getValue: function() {
  849.                     if(this.fieldRuleValue === null) {
  850.                         // Replace dynamic tags if enabled:
  851.                         if(this.af.autofillFormsPrefs.getBoolPref('enableDynamicTags'))
  852.                             this.fieldRuleValue = this.af.replaceDynamicTags(this.fieldRule['fieldRuleValue']);
  853.                         else
  854.                             this.fieldRuleValue = this.fieldRule['fieldRuleValue'];                
  855.                     }
  856.                     return this.fieldRuleValue;
  857.                 },
  858.                 getRule: function() {
  859.                     return this.fieldRule['fieldRuleFieldRule'];
  860.                 },
  861.                 isEnabled: function() {
  862.                     return this.fieldRule['fieldRuleEnabled'];
  863.                 },
  864.                 isURLMatching: function(url) {
  865.                     if(this.siteRuleRegExp === null) {
  866.                         this.siteRuleRegExp = new RegExp(this.fieldRule['fieldRuleSiteRule'],'i');
  867.                     }
  868.                     // Test if the siteRule matches the given URL:
  869.                     return this.siteRuleRegExp.test(url);
  870.                 },
  871.                 isRuleMatching: function(str) {
  872.                     if(this.fieldRuleRegExp === null) {
  873.                         this.fieldRuleRegExp = new RegExp(this.fieldRule['fieldRuleFieldRule'],'i');
  874.                     }
  875.                     // Test if the fieldRule matches the given string:
  876.                     return this.fieldRuleRegExp.test(str);
  877.                 },
  878.                 isValueMatching: function(str) {
  879.                     try {
  880.                         if(this.fieldRuleValueRegExp === null) {
  881.                             this.fieldRuleValueRegExp = new RegExp(this.getValue(),'i');
  882.                         }
  883.                         // Test if the value as regular expression matches the given string:
  884.                         return this.fieldRuleValueRegExp.test(str);
  885.                     } catch(e) {
  886.                         // If turning the value into a regular expression fails, compare the strings:
  887.                         return (str == this.getValue());
  888.                     }
  889.                 },
  890.                 isOverwrite: function() {
  891.                     // This setting defines if existing field contents should be overwritten
  892.                     // and if checkboxes and radio buttons should be checked or unchecked
  893.                     // and if selection options should be selected or unselected:
  894.                     return this.fieldRule['fieldRuleOverwrite']
  895.                 },
  896.                 getIndex: function() {
  897.                     return this.index;
  898.                 },
  899.                 getOptionsIndex: function() {
  900.                     return this.optionsIndex;
  901.                 },
  902.                 setOptionsIndex: function(optionsIndex) {
  903.                     this.optionsIndex = optionsIndex;
  904.                 },
  905.                 getElement: function() {
  906.                     return this.element;
  907.                 },
  908.                 setElement: function(element) {
  909.                     this.element = element;
  910.                 },
  911.                 clone: function() {
  912.                     // This creates only a shallow copy,
  913.                     // though we only need a shallow copy:
  914.                     var clone = new this.constructor();
  915.                     for(var key in this) {
  916.                         clone[key] = this[key];
  917.                     }
  918.                     return clone;
  919.                 }
  920.             }
  921.         }
  922.         if(this.fieldRuleAlternativesCache == null) {
  923.             this.fieldRuleAlternativesCache = new Object();
  924.         }
  925.         var identifier = this.getIndexForFieldRules(fieldRules)+'-'+index;
  926.         if(!this.fieldRuleAlternativesCache[identifier]) {
  927.             this.fieldRuleAlternativesCache[identifier] = new arguments.callee.fieldRuleAlternative(
  928.                 fieldRules,
  929.                 index
  930.             );
  931.         } else {
  932.             // Clone the cached alternative and set the clone as new cached element:
  933.             this.fieldRuleAlternativesCache[identifier] = this.fieldRuleAlternativesCache[identifier].clone()
  934.         }
  935.         return this.fieldRuleAlternativesCache[identifier];
  936.     },
  937.  
  938.     getLabelForElement: function(element) {
  939.         if(element.form && element.id) {
  940.             // Method to retrieve the textual content of the label assigned to the form element:
  941.             var labels = element.form.getElementsByTagName('label');
  942.             for(var i=0; i<labels.length; i++) {
  943.                 if(labels[i].htmlFor && labels[i].htmlFor == element.id) {
  944.                     // label elements may contain other inline elements,
  945.                     // so we just use the innerHTML content and strip it of all HTML tags
  946.                     // whitespace is removed from the beginning and end of the string for convenience:
  947.                     return this.trim(this.stripTags(labels[i].innerHTML));
  948.                 }
  949.             }
  950.         }
  951.         if(!this.autofillFormsPrefs.getBoolPref('labelsStrictMode')) {
  952.             return this.getLabelCloseToElement(element);
  953.         }
  954.         return null;
  955.     },
  956.     
  957.     getLabelCloseToElement: function(element) {
  958.         var label = null;
  959.         var node = element;
  960.         var nextNode;
  961.         if(element.type == 'checkbox' || element.type == 'radio') {
  962.             // For checkboxes and radio buttons the label is usually placed as nextSibling:
  963.             nextNode = 'nextSibling';
  964.         } else {
  965.             // For other elements the label is usually placed as previousSibling:
  966.             nextNode = 'previousSibling';
  967.         }
  968.         // Check if a sibling contains the element label:
  969.         while(node[nextNode]) {
  970.             node = node[nextNode];
  971.             label = this.getNodeTextContent(node, true);
  972.             if(label) {
  973.                 return label;
  974.             }
  975.         }
  976.         // Parse the siblings of the parentNode:
  977.         node = element.parentNode;
  978.         if(node) {
  979.             while(node[nextNode]) {
  980.                 node = node[nextNode];
  981.                 label = this.getNodeTextContent(node, true);
  982.                 if(label) {
  983.                     return label;
  984.                 }
  985.             }
  986.             // If the parentNode of the parentNode is a table cell,
  987.             // also parse the siblings of this node:
  988.             node = element.parentNode.parentNode;
  989.             if(node && node.nodeName == 'TD') {
  990.                 while(node[nextNode]) {
  991.                     node = node[nextNode];
  992.                     label = this.getNodeTextContent(node, true);
  993.                     if(label) {
  994.                         return label;
  995.                     }
  996.                 }
  997.             }
  998.         }
  999.         return null;
  1000.     },
  1001.     
  1002.     getNodeTextContent: function(node, trim) {
  1003.         // Get the text content from the current node or its child nodes:
  1004.         var text;        
  1005.         if(node.nodeType == 3) {
  1006.             // nodeType 3 is a text node:
  1007.             text = node.nodeValue;
  1008.         } else {
  1009.             // Do not follow selection nodes, script nodes or noscript nodes:
  1010.             if(node.nodeName == 'SELECT' || node.nodeName == 'SCRIPT' || node.nodeName == 'NOSCRIPT') {
  1011.                 return '';
  1012.             }
  1013.             text = '';
  1014.             for(var i=0; i<node.childNodes.length; i++) {
  1015.                 text += this.getNodeTextContent(node.childNodes[i]);
  1016.             }
  1017.         }
  1018.         if(trim) {
  1019.             return this.trim(text);
  1020.         } else {
  1021.             return text;
  1022.         }
  1023.     },
  1024.     
  1025.     applyFieldRulesOnElement: function(element,url,fieldRules) {
  1026.  
  1027.         var labelValue = this.autofillFormsPrefs.getBoolPref('matchAgainstLabels') ?
  1028.             this.getLabelForElement(element) : null;
  1029.  
  1030.         var positionString = this.autofillFormsPrefs.getBoolPref('matchAgainstPositions') ?
  1031.             this.currentFormIndex + this.autofillFormsPrefs.getCharPref('positionsIdentifier')
  1032.             + this.currentElementIndex : null;
  1033.             
  1034.         var fieldRuleAlternatives = new Array();
  1035.         
  1036.         // Go through the list of fieldRules:
  1037.         for(var i=0; i < fieldRules.length; i++) {
  1038.             
  1039.             var rule = this.fieldRuleAlternativeFactory(fieldRules, i);
  1040.             
  1041.             // Skip this rule if
  1042.             // a) the rule is disabled and disabled rules are to be ignored or
  1043.             // b) the current URL is not matching the siteRule or
  1044.             // c) all of the following are false:
  1045.             //     1) the element name does not match the fieldRule
  1046.             //     2) label matching is disabled or the element label does not match the fieldRule
  1047.             //     3) the element name is not empty or the element id does not match the fieldRule
  1048.             //    4) position matching is disabled or the position does not match the fieldRule
  1049.             if(    !rule.isEnabled() &&
  1050.                 this.autofillFormsPrefs.getBoolPref('ignoreDisabledRulesOnAutofill') ||
  1051.                 !rule.isURLMatching(url) ||
  1052.                 (
  1053.                     !rule.isRuleMatching(element.name) &&
  1054.                     (labelValue === null || !rule.isRuleMatching(labelValue)) &&
  1055.                     (element.name || !rule.isRuleMatching(element.id)) &&
  1056.                     (positionString === null || !rule.isRuleMatching(positionString))
  1057.                 )
  1058.                 ) {
  1059.                 if(fieldRuleAlternatives.length > 0) {
  1060.                     // Break out of the loop, if we already have an alternative:
  1061.                     break;
  1062.                 } else {
  1063.                     continue;
  1064.                 }
  1065.             }
  1066.     
  1067.             if(element.type == 'select-one' || element.type == 'select-multiple') {
  1068.                 // Go through the selection options:
  1069.                 for(var j = 0; j < element.options.length; j++) {
  1070.                     // Match either the value or the text (the selection option label):
  1071.                     if(rule.isValueMatching(element.options[j].value) || rule.isValueMatching(element.options[j].text)) {
  1072.                         // Remember the matching option:
  1073.                         rule.setOptionsIndex(j);
  1074.                         // Remember the element:
  1075.                         rule.setElement(element);
  1076.                         // Add a clone of the alternative and continue to see if the value matches several options:
  1077.                         fieldRuleAlternatives.push(rule.clone());
  1078.                     }
  1079.                 }
  1080.             } else if(element.type == 'checkbox' || element.type == 'radio') {
  1081.                 if(rule.isValueMatching(element.value)) {
  1082.                     // Remember the element:
  1083.                     rule.setElement(element);
  1084.                     // Add the alternative:
  1085.                     fieldRuleAlternatives.push(rule);
  1086.                     // Only one rule has to match a checkbox/radio button, so we break out of the loop:
  1087.                     break;
  1088.                 }
  1089.             } else {                
  1090.                 // Remember the element:
  1091.                 rule.setElement(element);
  1092.                 // Add the alternative:
  1093.                 fieldRuleAlternatives.push(rule);
  1094.             }
  1095.         }
  1096.  
  1097.         return this.applyFieldRulesAlternativesOnElement(element,fieldRuleAlternatives);
  1098.     },
  1099.  
  1100.     applyFieldRulesAlternativesOnElement: function(element,fieldRuleAlternatives) {
  1101.         if(fieldRuleAlternatives.length == 0) {
  1102.             return false;
  1103.         }
  1104.         
  1105.         // Use all alternatives for select-multiple elements:
  1106.         if(element.type == 'select-multiple') {
  1107.             for(var i=0; i < fieldRuleAlternatives.length; i++) {
  1108.                 var rule = fieldRuleAlternatives[i];                
  1109.                 if(rule.isOverwrite()) {
  1110.                     element.options[rule.getOptionsIndex()].selected = true;
  1111.                 } else {
  1112.                     element.options[rule.getOptionsIndex()].selected = false;
  1113.                 }
  1114.             }
  1115.             return true;
  1116.         }
  1117.         
  1118.         // Select the alternatives index (displays a selection dialog if required):
  1119.         var index = this.selectFieldRulesAlternativesIndex(fieldRuleAlternatives);
  1120.         
  1121.         if(index == -1) {
  1122.             return false;
  1123.         } else {
  1124.             var rule = fieldRuleAlternatives[index];
  1125.             if(element.type == 'select-one') {
  1126.                 if(rule.isOverwrite()) {
  1127.                     element.options[rule.getOptionsIndex()].selected = true;
  1128.                 } else {
  1129.                     element.options[rule.getOptionsIndex()].selected = false;
  1130.                 }                
  1131.             } else if(element.type == 'checkbox') {
  1132.                 if(rule.isOverwrite()) {
  1133.                     element.checked = true;
  1134.                 } else {
  1135.                     element.checked = false;
  1136.                 }
  1137.             } else if(element.type == 'radio') {
  1138.                 try {
  1139.                     // Rules matching radio elements are stored and handled as group
  1140.                     // at the end of each form loop with the applyStoredFieldRulesAlternatives method:
  1141.                     if(!this.fieldRuleAlternativesHash[element.name]) {
  1142.                         this.fieldRuleAlternativesHash[element.name] = new Array();
  1143.                     }
  1144.                     this.fieldRuleAlternativesHash[element.name].push(rule);
  1145.                 } catch(e) {
  1146.                     this.log(e);
  1147.                     return false;
  1148.                 }
  1149.             } else {
  1150.                 if(!element.value || rule.isOverwrite()) {
  1151.                     if(element.type == 'textarea') {
  1152.                         // Replace control character placeholders:
  1153.                         element.value = this.replaceControlCharacterPlaceholders(rule.getValue());
  1154.                     } else {
  1155.                         element.value = rule.getValue();
  1156.                     }
  1157.                 }
  1158.             }
  1159.         }
  1160.         return true;        
  1161.     },
  1162.  
  1163.     applyStoredFieldRulesAlternatives: function() {
  1164.         for(var key in this.fieldRuleAlternativesHash) {
  1165.             var fieldRuleAlternatives = this.filterRealFieldRuleAlternatives(
  1166.                 this.fieldRuleAlternativesHash[key]
  1167.             );
  1168.             var index = this.selectFieldRulesAlternativesIndex(fieldRuleAlternatives);
  1169.             if(index != -1) {
  1170.                 var rule = fieldRuleAlternatives[index];
  1171.                 // This is currently only used for radio input fields:
  1172.                 if(rule.isOverwrite()) {
  1173.                     rule.getElement().checked = true;
  1174.                 } else {
  1175.                     rule.getElement().checked = false;
  1176.                 }
  1177.             }
  1178.         }
  1179.     },
  1180.     
  1181.     filterRealFieldRuleAlternatives: function(fieldRuleAlternatives) {
  1182.         // Sort the fieldRuleAlternatives by index:
  1183.         fieldRuleAlternatives.sort(this.compareFieldRuleAlternativesByIndex);
  1184.         // Make sure only real Alternatives (placed next to each other) are included:
  1185.         for(var i=1; i<fieldRuleAlternatives.length; i++) {
  1186.             // If the fieldRules index is more than one step larger than the previous one,
  1187.             // the remaining array items can be sliced off - they are no real Alternatives:
  1188.             if(fieldRuleAlternatives[i].getIndex()-1 > fieldRuleAlternatives[i-1].getIndex()) {
  1189.                 fieldRuleAlternatives = fieldRuleAlternatives.slice(0, i);
  1190.                 break;
  1191.             }
  1192.         }
  1193.         return fieldRuleAlternatives;
  1194.     },
  1195.     
  1196.     compareFieldRuleAlternativesByIndex: function(ruleA, ruleB) {
  1197.         if(ruleA.getIndex() < ruleB.getIndex()) {
  1198.             return -1;
  1199.         } else if(ruleA.getIndex() > ruleB.getIndex()) {
  1200.             return 1;
  1201.         }
  1202.         return 0;
  1203.     },
  1204.  
  1205.     getFieldRulesAlternativeLabel: function(rule) {
  1206.         // This method returns a label for this alternative
  1207.         // to be displayed on the alternatives selection
  1208.         switch(rule.getElement().type) {
  1209.             case 'select-multiple':
  1210.             case 'select-one':
  1211.                 // Use the options text:
  1212.                 return rule.getElement().options[rule.getOptionsIndex()].text;
  1213.             case 'radio':
  1214.             case 'checkbox':
  1215.                 // Try to retrieve the element label:
  1216.                 var label = this.getLabelForElement(rule.getElement());
  1217.                 // Remove the colon, if present:
  1218.                 if(label && label.charAt(label.length-1) == ':') {
  1219.                     label = label.substr(0, label.length-1);
  1220.                 }
  1221.                 // If no label could be found,
  1222.                 // use the element value:
  1223.                 if(!label) {
  1224.                     label = rule.getElement().value;
  1225.                 }
  1226.                 return label;
  1227.             default:
  1228.                 // Use the calculated value:
  1229.                 return rule.getValue();
  1230.         }
  1231.     },
  1232.     
  1233.     selectFieldRulesAlternativesIndex: function(fieldRuleAlternatives) {                    
  1234.         // Display a selection prompt if we have alternatives and no alternativesIndex has been set yet
  1235.         // or the rememberAlternativesIndex setting is false or the saved alternativesLength is different:
  1236.         if(fieldRuleAlternatives.length > 1) {
  1237.             if(this.autofillFormsPrefs.getBoolPref('rememberAlternativesIndex') == false
  1238.                 || this.fieldRuleAlternativesIndex === null
  1239.                 || fieldRuleAlternatives.length != this.fieldRuleAlternativesLength) {
  1240.                 // The selection list displays the index number and the current fieldRuleValues:
  1241.                 var list = new Array();
  1242.                 var maxFigureLength = fieldRuleAlternatives.length.toString().length;
  1243.                 for(var i=0; i < fieldRuleAlternatives.length; i++) {
  1244.                     list.push(
  1245.                         this.addLeadingZeros(i+1, maxFigureLength)
  1246.                         + '. '
  1247.                         + this.getFieldRulesAlternativeLabel(fieldRuleAlternatives[i])
  1248.                     );
  1249.                 }
  1250.                 var selected = {};
  1251.                 // Show the selection prompt:
  1252.                 var ok = this.getPrompts().select(
  1253.                     window,
  1254.                     this.getStringBundle().getString('alternativesSelectionWindowTitle'),
  1255.                     this.getStringBundle().getString('alternativesSelectionPrompt'),
  1256.                     list.length,
  1257.                     list,
  1258.                     selected
  1259.                 );
  1260.                 // Save the selected alternatives index, return -1 on cancel:
  1261.                 if(ok)
  1262.                     this.fieldRuleAlternativesIndex = selected.value;
  1263.                 else
  1264.                     return -1;
  1265.                     
  1266.                 this.fieldRuleAlternativesLength = fieldRuleAlternatives.length;
  1267.             }
  1268.             // Use the fieldRuleAlternative with the selected fieldRuleAlternativesIndex:
  1269.             return this.fieldRuleAlternativesIndex;
  1270.         } else if(fieldRuleAlternatives.length == 1) {
  1271.             return 0
  1272.         }
  1273.         return -1;
  1274.     },
  1275.     
  1276.     stripTags: function(str) {
  1277.         if (!arguments.callee.regExp) {
  1278.             arguments.callee.regExp = new RegExp('<\\/?[^>]+?>', 'g');
  1279.         }
  1280.         // Return string stripped from HTML tags:
  1281.         return str.replace(arguments.callee.regExp, '');
  1282.     },
  1283.     
  1284.     trim: function(str) {
  1285.         if (!arguments.callee.regExp) {
  1286.             arguments.callee.regExp = new RegExp('(?:^\\s+)|(?:\\s+$)', 'g');
  1287.         }
  1288.         // Return string with whitespace removed at beginning and end of the string:
  1289.         return str.replace(arguments.callee.regExp, '');
  1290.     },
  1291.  
  1292.     initProfilesPopupMenu: function(event) {
  1293.         var menupopup = event.target;
  1294.         // Remove all children nodes:
  1295.         while(menupopup.hasChildNodes()) {
  1296.             menupopup.removeChild(menupopup.firstChild);
  1297.         }
  1298.         // Add the profile labels as menu items:
  1299.         for(var i=0; i < this.getProfileLabels().length; i++) {
  1300.             var menuitem = document.createElement('menuitem');
  1301.             menuitem.setAttribute('label', this.getProfileLabel(i));
  1302.             menuitem.setAttribute('oncommand', 'autofillForms.setProfileIndex('+i+');');
  1303.             menuitem.setAttribute('type', 'radio');
  1304.             if(i == this.getProfileIndex()) {
  1305.                 menuitem.setAttribute('checked', true);
  1306.             }
  1307.             menupopup.appendChild(menuitem);
  1308.         }
  1309.     },
  1310.  
  1311.     initManualFillContextMenu: function(event) {
  1312.         var menupopup = event.target;
  1313.         // Remove all children nodes:
  1314.         while(menupopup.hasChildNodes()) {
  1315.             menupopup.removeChild(menupopup.firstChild);
  1316.         }
  1317.  
  1318.         var authenticationNeeded = false;
  1319.         if(this.autofillFormsPrefs.getBoolPref('storeEncrypted')) {
  1320.             // Determine if a master password is set and the user has not been authenticated yet:
  1321.             authenticationNeeded = this.getMasterSecurityDevice().getInternalKeyToken().needsLogin()
  1322.                                     && !this.getMasterSecurityDevice().getInternalKeyToken().isLoggedIn();
  1323.         }
  1324.  
  1325.         if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  1326.             // Always retrieve the profile labels from file if useConfigDirectory is enabled:
  1327.             this.profileLabels = null;
  1328.         }
  1329.  
  1330.         // Check if only one profile is to be shown:
  1331.         if((this.getFormFieldsContextMenuProfileIndex() != -3) || (this.getProfileLabels().length == 1)) {
  1332.             var profileIndex = (this.getFormFieldsContextMenuProfileIndex() == -2)
  1333.                 ? this.getProfileIndex() : this.getFormFieldsContextMenuProfileIndex();
  1334.             if(authenticationNeeded) {                
  1335.                 var menuitem = document.createElement('menuitem');
  1336.                 menuitem.setAttribute('label', this.getProfileLabel(profileIndex)+'...');
  1337.                 menuitem.setAttribute(
  1338.                     'oncommand',
  1339.                     'autofillForms.authenticateAndShowManualFillDialog('+profileIndex+');'
  1340.                 );
  1341.                 menupopup.appendChild(menuitem);
  1342.             } else {
  1343.                 this.initManualFillProfileContextMenu(event, profileIndex);
  1344.             }
  1345.             return;
  1346.         }
  1347.         
  1348.         // Add the profile labels as menus or menuitems if authentication is needed:
  1349.         for(var i=0; i < this.getProfileLabels().length; i++) {
  1350.             if(authenticationNeeded) {
  1351.                 var menuitem = document.createElement('menuitem');
  1352.                 menuitem.setAttribute('label', this.getProfileLabel(i)+'...');
  1353.                 menuitem.setAttribute(
  1354.                     'oncommand',
  1355.                     'autofillForms.authenticateAndShowManualFillDialog('+i+');'
  1356.                 );
  1357.                 menupopup.appendChild(menuitem);
  1358.             } else {
  1359.                 var menu = document.createElement('menu');
  1360.                 menu.setAttribute('label', this.getProfileLabel(i));
  1361.     
  1362.                 // Add a menupopup for each profile:
  1363.                 var profilemenupopup = document.createElement('menupopup');
  1364.                 profilemenupopup.setAttribute(
  1365.                     'onpopupshowing',
  1366.                     'if(event.target == this) autofillForms.initManualFillProfileContextMenu(event, '+i+');'
  1367.                 );
  1368.                 menu.appendChild(profilemenupopup);
  1369.                 menupopup.appendChild(menu);
  1370.             }
  1371.         }
  1372.     },
  1373.  
  1374.     initManualFillProfileContextMenu: function(event, profileID) {
  1375.         var menupopup = event.target;
  1376.         // Remove all children nodes:
  1377.         while(menupopup.hasChildNodes()) {
  1378.             menupopup.removeChild(menupopup.firstChild);
  1379.         }
  1380.         var menuPopupMore;
  1381.         // Add the profile field rules as menu items:
  1382.         for(var i=0; i < this.getFieldRules(profileID).length; i++) {
  1383.             var menuitem = document.createElement('menuitem');
  1384.             menuitem.setAttribute('label', this.getFieldRules(profileID)[i]['fieldRuleName']);
  1385.             menuitem.setAttribute('oncommand', 'autofillForms.fillTargetFormField('+profileID+','+i+');');
  1386.             if(this.getFieldRules(profileID)[i]['fieldRuleEnabled']) {
  1387.                 menupopup.appendChild(menuitem);
  1388.             } else {
  1389.                 // Add disabled items to a "More..." menu:
  1390.                 if(!menuPopupMore) {
  1391.                     menuPopupMore = document.createElement('menupopup');
  1392.                 }
  1393.                 menuPopupMore.appendChild(menuitem);
  1394.             }
  1395.         }
  1396.         if(menuPopupMore) {
  1397.             if(!menupopup.hasChildNodes()) {
  1398.                 // All field rules of this profile are disabled, so no need to create a submenu:
  1399.                 while(menuPopupMore.hasChildNodes()) {
  1400.                     // appendChild removes the node from the current parent node
  1401.                     // and adds it to the new parent node:
  1402.                     menupopup.appendChild(menuPopupMore.firstChild);
  1403.                 }
  1404.             } else {
  1405.                 // Append the "More..." menu:
  1406.                 var menuMore = document.createElement('menu');
  1407.                 menuMore.setAttribute('label', this.getStringBundle().getString('contextMenuMore'));
  1408.                 menuMore.appendChild(menuPopupMore);
  1409.                 menupopup.appendChild(menuMore);
  1410.             }
  1411.         }
  1412.         // Reset object to release used memory:
  1413.         this.fieldRules = null;
  1414.     },
  1415.     
  1416.     authenticateAndShowManualFillDialog: function(profileID) {
  1417.         try {
  1418.             Components.classes['@mozilla.org/security/pk11tokendb;1']
  1419.                 .getService(Components.interfaces.nsIPK11TokenDB).getInternalKeyToken().login(false);
  1420.  
  1421.             var prompts = Components.classes['@mozilla.org/embedcomp/prompt-service;1']
  1422.                                     .getService(Components.interfaces.nsIPromptService);
  1423.             // The selection and the subselection lists:
  1424.             var list = new Array();
  1425.             var listMore;
  1426.             // Hashs mapping the list positions to the original indices:
  1427.             var listIndexMapping = new Object();;
  1428.             var listMoreIndexMapping;
  1429.             for(var i=0; i < this.getFieldRules(profileID).length; i++) {
  1430.                 if(this.getFieldRules(profileID)[i]['fieldRuleEnabled']) {
  1431.                     list.push(this.getFieldRules(profileID)[i]['fieldRuleName']);
  1432.                     listIndexMapping[list.length-1] = i;
  1433.                 } else {
  1434.                     // Add disabled items to a "More..." list:
  1435.                     if(!listMore) {
  1436.                         listMore = new Array();
  1437.                         listMoreIndexMapping = new Object();
  1438.                     }
  1439.                     listMore.push(this.getFieldRules(profileID)[i]['fieldRuleName']);
  1440.                     listMoreIndexMapping[listMore.length-1] = i;
  1441.                 }
  1442.             }
  1443.             if(listMore) {
  1444.                 // If all field rules of this profile are disabled, there is no need of a sublist:
  1445.                 if(!list.length) {
  1446.                     list = listMore;
  1447.                     listIndexMapping = listMoreIndexMapping;
  1448.                     listMore = null;
  1449.                     listMoreIndexMapping = null;
  1450.                 } else {
  1451.                     list.push(this.getStringBundle().getString('contextMenuMore'));                    
  1452.                 }
  1453.             }
  1454.             var selected = {};
  1455.             var ok = Components.classes['@mozilla.org/embedcomp/prompt-service;1']
  1456.                         .getService(Components.interfaces.nsIPromptService)
  1457.                         .select(
  1458.                             window,
  1459.                             null, // Window title - defaults to locale version of "Select"
  1460.                             null, // Prompt text - defaults to empty string
  1461.                             list.length,
  1462.                             list,
  1463.                             selected
  1464.                         );
  1465.             if(ok) {
  1466.                 // If "More..." is selected, show the disabled items as selection list:
  1467.                 if(listMore && selected.value == list.length-1) {
  1468.                     selected = {};
  1469.                     ok = Components.classes['@mozilla.org/embedcomp/prompt-service;1']
  1470.                         .getService(Components.interfaces.nsIPromptService)
  1471.                         .select(
  1472.                             window,
  1473.                             null, // Window title - defaults to locale version of "Select"
  1474.                             null, // Prompt text - defaults to empty string
  1475.                             listMore.length,
  1476.                             listMore,
  1477.                             selected
  1478.                         );
  1479.                     if(ok) {
  1480.                         this.fillTargetFormField(
  1481.                             profileID,
  1482.                             listMoreIndexMapping[selected.value]
  1483.                         );
  1484.                     }
  1485.                 } else {
  1486.                     this.fillTargetFormField(
  1487.                         profileID,
  1488.                         listIndexMapping[selected.value]
  1489.                     );
  1490.                 }
  1491.             }
  1492.         } catch(e) {
  1493.             // Authentication with master security device failed
  1494.         }
  1495.         // Reset object to release used memory:
  1496.         this.fieldRules = null;
  1497.     },
  1498.     
  1499.     fillTargetFormField: function(profileID, ruleID) {
  1500.         if(this.targetFormField) {
  1501.             var value = this.getFieldRules(profileID)[ruleID]['fieldRuleValue'];
  1502.             // Replace dynamic tags if enabled:
  1503.             if(this.autofillFormsPrefs.getBoolPref('enableDynamicTags')) {
  1504.                 value = this.replaceDynamicTags(value);
  1505.             }
  1506.             try {
  1507.                 // Try to use selection information:
  1508.                 var newCursorPos = this.targetFormField.selectionStart + value.length;            
  1509.                 this.targetFormField.value =     this.targetFormField.value.substr(0, this.targetFormField.selectionStart)
  1510.                                                 + value
  1511.                                                 + this.targetFormField.value.substr(this.targetFormField.selectionEnd);    
  1512.                 // Adjust the cursor position:
  1513.                 this.targetFormField.selectionEnd = newCursorPos;
  1514.                 this.targetFormField.selectionStart = newCursorPos;
  1515.             } catch(e) {
  1516.                 // This input field does not support selections - just try to set the value:
  1517.                 try {
  1518.                     this.targetFormField.value = value;
  1519.                 } catch(e) {
  1520.                     // Catch errors if value could not be set on the form field
  1521.                 }
  1522.             }
  1523.             // Reset objects to release used memory:
  1524.             this.fieldRules = null;
  1525.             this.dynamicTags = null;
  1526.             this.dynamicTagCodes = null;
  1527.         }
  1528.     },
  1529.     
  1530.     tooltip: function(event) {
  1531.         if (!document.tooltipNode) {
  1532.             return;
  1533.         }        
  1534.         // Get the tooltip node:
  1535.         var tooltip = document.getElementById('autofillFormsTooltip');
  1536.         if(tooltip) {
  1537.             // Add the associated tooltip content for each toolbar button menu item, toolbar button and statusbar icon:
  1538.             if(document.tooltipNode.id == 'autofillFormsButton' || document.tooltipNode.id == 'autofillFormsPanelIcon') {
  1539.                 if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  1540.                     // Always retrieve the profile labels from file if useConfigDirectory is enabled:
  1541.                     this.profileLabels = null;
  1542.                     this.tooltipCurrentProfile = null;
  1543.                 }                
  1544.                 if(!this.tooltipCurrentProfile || !this.tooltipGrid) {
  1545.                     // Remove all children nodes:
  1546.                     while(tooltip.hasChildNodes()) {
  1547.                         tooltip.removeChild(tooltip.firstChild);
  1548.                     }
  1549.                     // Add the current profile label:
  1550.                     tooltip.appendChild(this.getTooltipCurrentProfile());
  1551.                     // Add the tooltip grid with the command labels, mouse buttons and keyboard shortcuts:
  1552.                     tooltip.appendChild(this.getTooltipGrid());
  1553.                 }
  1554.             } else {
  1555.                 // Don't show tooltips for the toolbar button menu items:
  1556.                 event.preventDefault();
  1557.             }
  1558.         }
  1559.     },
  1560.  
  1561.     getTooltipCurrentProfile: function() {
  1562.         if(!this.tooltipCurrentProfile) {
  1563.             var hbox = document.createElement('hbox');
  1564.             hbox.setAttribute(
  1565.                 'id',
  1566.                 'autofillFormsTooltipCurrentProfile'
  1567.             );            
  1568.             var label = document.createElement('label');
  1569.             label.setAttribute(
  1570.                 'value',
  1571.                 this.getStringBundle().getString('currentProfileLabel')
  1572.             );
  1573.             label.setAttribute(
  1574.                 'id',
  1575.                 'autofillFormsTooltipCurrentProfileCaption'
  1576.             );
  1577.             hbox.appendChild(label);
  1578.             label = label.cloneNode(false);
  1579.             label.setAttribute(
  1580.                 'value',
  1581.                 this.getProfileLabel(this.getProfileIndex())
  1582.             );
  1583.             label.setAttribute(
  1584.                 'id',
  1585.                 'autofillFormsTooltipCurrentProfileLabel'
  1586.             );
  1587.             hbox.appendChild(label);
  1588.             this.tooltipCurrentProfile = hbox;
  1589.         }
  1590.         return this.tooltipCurrentProfile;
  1591.     },
  1592.     
  1593.     getTooltipGrid: function() {
  1594.         if(!this.tooltipGrid) {
  1595.             var commands = new Array();
  1596.             for(var property in this.shortcut) {
  1597.                 commands.push(new Array(
  1598.                     this.getStringBundle().getString('tooltip'+property.replace(/shortcut/,'')),
  1599.                     this.getFormattedMouseButton(this.getMouseButton('mouseS'+property.substr(1))),
  1600.                     this.getFormattedShortcut(this.getShortcut(property))
  1601.                 ));
  1602.             }
  1603.             var grid = document.createElement('grid');
  1604.             grid.setAttribute(
  1605.                 'id',
  1606.                 'autofillFormsTooltipGrid'
  1607.             );
  1608.             var columns = document.createElement('columns');
  1609.             var column = document.createElement('column');
  1610.             var rows = document.createElement('rows');
  1611.             var row = document.createElement('row');
  1612.             var label = document.createElement('label');
  1613.             columns.appendChild(column);
  1614.             columns.appendChild(column.cloneNode(false));
  1615.             columns.appendChild(column.cloneNode(false));
  1616.             grid.appendChild(columns);
  1617.             // Create the column headers:
  1618.             label.setAttribute(
  1619.                 'class',
  1620.                 'autofillFormsTooltipGridHeader'
  1621.             );
  1622.             label.setAttribute(
  1623.                 'value',
  1624.                 this.getStringBundle().getString('command')
  1625.             );
  1626.             row.appendChild(label);
  1627.             label = label.cloneNode(false);
  1628.             label.setAttribute(
  1629.                 'value',
  1630.                 this.getStringBundle().getString('mousebutton')
  1631.             );
  1632.             row.appendChild(label);
  1633.             label = label.cloneNode(false);
  1634.             label.setAttribute(
  1635.                 'value',
  1636.                 this.getStringBundle().getString('keyboardShortcut')
  1637.             );
  1638.             row.appendChild(label);
  1639.             rows.appendChild(row);
  1640.             // Create a row for each command:
  1641.             for(var i=0; i<commands.length; i++) {
  1642.                 row = row.cloneNode(false);
  1643.                 // Skip if neither mouseButton nor keyboard shortcut is set:
  1644.                 if(!commands[i][1] && !commands[i][2]) {
  1645.                     continue;
  1646.                 }
  1647.                 for(var j=0; j<commands[i].length; j++) {
  1648.                     label = label.cloneNode(false);
  1649.                     label.setAttribute(
  1650.                         'value',
  1651.                         commands[i][j]
  1652.                     );
  1653.                     if(j == 0) {
  1654.                         label.setAttribute(
  1655.                             'class',
  1656.                             'autofillFormsTooltipGridCommand'
  1657.                         );
  1658.                     } else if(j == 1) {
  1659.                         label.setAttribute(
  1660.                             'class',
  1661.                             'autofillFormsTooltipGridMouseButton'
  1662.                         );
  1663.                     } else {
  1664.                         label.setAttribute(
  1665.                             'class',
  1666.                             'autofillFormsTooltipGridKeyboardShortcut'
  1667.                         );
  1668.                     }
  1669.                     row.appendChild(label);
  1670.                 }
  1671.                 rows.appendChild(row);
  1672.             }
  1673.             grid.appendChild(rows);
  1674.             this.tooltipGrid = grid;
  1675.         }
  1676.         return this.tooltipGrid;
  1677.     },
  1678.  
  1679.     resetAllProfiles: function() {
  1680.         if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
  1681.             // Confirmation dialog:
  1682.             if(!this.getPrompts().confirm(
  1683.                     null,
  1684.                     this.getStringBundle().getString('resetAllProfilesTitle'),
  1685.                     this.getStringBundle().getString('resetAllProfilesText')
  1686.                 )
  1687.             ) {
  1688.                 return;    
  1689.             }
  1690.         }
  1691.         
  1692.         // Reset the user preferences:
  1693.         if(this.autofillFormsPrefs.prefHasUserValue('useConfigDirectory')) {
  1694.             this.autofillFormsPrefs.clearUserPref('useConfigDirectory');
  1695.         }
  1696.         if(this.autofillFormsPrefs.prefHasUserValue('storeEncrypted')) {
  1697.             this.autofillFormsPrefs.clearUserPref('storeEncrypted');
  1698.         }
  1699.         if(this.autofillFormsPrefs.prefHasUserValue('profileIndex')) {
  1700.             this.autofillFormsPrefs.clearUserPref('profileIndex');
  1701.         }
  1702.         if(this.autofillFormsPrefs.prefHasUserValue('profileLabels')) {
  1703.             this.autofillFormsPrefs.clearUserPref('profileLabels');
  1704.         }
  1705.         if(this.autofillFormsPrefs.prefHasUserValue('profileSiteRules')) {
  1706.             this.autofillFormsPrefs.clearUserPref('profileSiteRules');
  1707.         }
  1708.         if(this.autofillFormsPrefs.prefHasUserValue('fieldRules')) {
  1709.             this.autofillFormsPrefs.clearUserPref('fieldRules');
  1710.         }
  1711.  
  1712.         this.profileIndex = null;
  1713.         this.profileLabels = null;
  1714.         this.profileSiteRules = null;
  1715.         this.fieldRules = null;
  1716.         
  1717.         // Re-init the profiles lists:
  1718.         this.initProfilesLists();
  1719.         // Re-init the fieldRules tree:
  1720.         this.initTree();
  1721.         // Re-initialize the simple interface:
  1722.         this.initSimpleInterface();
  1723.         
  1724.         if(this.tree && this.selection) {
  1725.             try {
  1726.                 // Clear out the fieldRules tree selections
  1727.                 this.selection.select(-1);
  1728.             } catch(e) {
  1729.                 this.log(e);
  1730.             }
  1731.         }
  1732.     },
  1733.     
  1734.     initProfilesLists: function(event) {
  1735.         // The profiles tree:
  1736.         this.initProfilesTree();
  1737.         
  1738.         // Editable profiles menu list:
  1739.         var profilesMenuList = document.getElementById('profilesMenuList');
  1740.         if(profilesMenuList) {
  1741.             profilesMenuList.removeAllItems();
  1742.             for(var i=0; i < this.getProfileLabels().length; i++) {
  1743.                 profilesMenuList.appendItem(
  1744.                     this.getProfileLabel(i)
  1745.                 );
  1746.             }
  1747.             profilesMenuList.selectedIndex = this.getProfileIndex();
  1748.         }
  1749.         // Simple interface profiles menu list:
  1750.         var simpleInterfaceProfileMenuList = document.getElementById('simpleInterfaceProfileMenuList');
  1751.         if(simpleInterfaceProfileMenuList) {
  1752.             simpleInterfaceProfileMenuList.removeAllItems();
  1753.             for(var i=0; i < this.getProfileLabels().length; i++) {
  1754.                 simpleInterfaceProfileMenuList.appendItem(
  1755.                     this.getProfileLabel(i)
  1756.                 );
  1757.             }
  1758.             simpleInterfaceProfileMenuList.selectedIndex = this.getProfileIndex();
  1759.         }
  1760.         // Global profile selection:
  1761.         var globalProfileMenuList = document.getElementById('globalProfileMenuList');
  1762.         if(globalProfileMenuList) {
  1763.             globalProfileMenuList.removeAllItems();
  1764.             for(var i=0; i < this.getProfileLabels().length; i++) {
  1765.                 globalProfileMenuList.appendItem(
  1766.                     this.getProfileLabel(i)
  1767.                 );
  1768.             }
  1769.             globalProfileMenuList.selectedIndex = this.getGlobalProfileIndex();
  1770.         }
  1771.         // Form fields context menu selection:
  1772.         var contextMenuProfileMenuList = document.getElementById('contextMenuProfileMenuList');
  1773.         if(contextMenuProfileMenuList) {
  1774.             // The first 3 items are "All profiles", "Active profile" and a menuseparator:
  1775.             while(contextMenuProfileMenuList.firstChild.childNodes[3]) {
  1776.                 // The more convenient getItemAtIndex does not seem to work with Firefox versions < 3,
  1777.                 // so we use DOM methods on the menupopup child node of the menu node instead:
  1778.                 contextMenuProfileMenuList.firstChild.removeChild(
  1779.                     contextMenuProfileMenuList.firstChild.childNodes[3]
  1780.                 );
  1781.             }
  1782.             for(var i=0; i < this.getProfileLabels().length; i++) {
  1783.                 contextMenuProfileMenuList.appendItem(
  1784.                     this.getProfileLabel(i)
  1785.                 );
  1786.             }
  1787.             contextMenuProfileMenuList.selectedIndex
  1788.                 = this.getFormFieldsContextMenuProfileIndex()+3;
  1789.         }
  1790.         
  1791.         // The profile site rule textbox:
  1792.         this.initProfileSiteRuleTextBox();
  1793.     },
  1794.     
  1795.     updateProfilesLists: function() {
  1796.         // The more convenient getItemAtIndex does not seem to work with Firefox versions < 3,
  1797.         // so we use DOM methods on the menupopup child node of the menu nodes instead:
  1798.         
  1799.         // Editable profiles menu list:
  1800.         var profilesMenuList = document.getElementById('profilesMenuList');
  1801.         if(profilesMenuList) {
  1802.             profilesMenuList
  1803.                 .firstChild.childNodes[this.getProfileIndex()].label
  1804.                 = this.getProfileLabel(this.getProfileIndex());
  1805.         }
  1806.         // Simple interface profiles menu list:
  1807.         var simpleInterfaceProfileMenuList = document.getElementById('simpleInterfaceProfileMenuList');
  1808.         if(simpleInterfaceProfileMenuList) {
  1809.             simpleInterfaceProfileMenuList
  1810.                 .firstChild.childNodes[this.getProfileIndex()].label
  1811.                 = this.getProfileLabel(this.getProfileIndex());
  1812.         }
  1813.         // Global profile selection:
  1814.         var globalProfileMenuList = document.getElementById('globalProfileMenuList');
  1815.         if(globalProfileMenuList) {
  1816.             globalProfileMenuList
  1817.                 .firstChild.childNodes[this.getProfileIndex()].label
  1818.                 = this.getProfileLabel(this.getProfileIndex());
  1819.         }
  1820.         // Form fields context menu selection:
  1821.         var contextMenuProfileMenuList = document.getElementById('contextMenuProfileMenuList');
  1822.         if(contextMenuProfileMenuList) {
  1823.             // The first 3 items are "All profiles", "Active profile" and a menuseparator:
  1824.             contextMenuProfileMenuList
  1825.                 .firstChild.childNodes[this.getProfileIndex()+3].label
  1826.                 = this.getProfileLabel(this.getProfileIndex());
  1827.         }
  1828.         // The profiles tree:
  1829.         if(this.profilesTreeBox) {
  1830.             this.profilesTreeBox.invalidateRow(this.getProfileIndex());
  1831.         }
  1832.     },
  1833.     
  1834.     getProfileIndex: function() {
  1835.         if(this.profileIndex == null)
  1836.             this.profileIndex = this.autofillFormsPrefs.getIntPref('profileIndex');
  1837.         return this.profileIndex;
  1838.     },
  1839.     
  1840.     setProfileIndex: function(index) {
  1841.         if(this.profileIndex == index)
  1842.             return;
  1843.             
  1844.         // See method selectedFieldRule() why this has to be set to null:
  1845.         this.lastSelectedIndex = null;
  1846.         
  1847.         this.autofillFormsPrefs.setIntPref('profileIndex',parseInt(index));
  1848.         // Update the tree view if present:
  1849.         if(this.tree) {
  1850.             // The settings page doesn't observe preferences changes - set the profileIndex manually:
  1851.             this.profileIndex = index;
  1852.             // Re-init the tree:
  1853.             this.initTree();
  1854.             // Re-initialize the simple interface:
  1855.             this.initSimpleInterface();
  1856.         }
  1857.         // Update the profiles tree selection if present and not already updated:
  1858.         if(this.profilesTree && this.profilesSelection.currentIndex != index) {
  1859.             // Select the current profile:
  1860.             this.profilesSelection.select(index);
  1861.     
  1862.             // Ensure row is visible (scrolls if not):
  1863.             this.profilesTreeBox.ensureRowIsVisible(index);
  1864.         }
  1865.         // Editable profiles menu list:
  1866.         var profilesMenuList = document.getElementById('profilesMenuList');
  1867.         if(profilesMenuList) {
  1868.             profilesMenuList.selectedIndex = this.getProfileIndex();
  1869.         }
  1870.         // Simple interface profiles menu list:
  1871.         var simpleInterfaceProfileMenuList = document.getElementById('simpleInterfaceProfileMenuList');
  1872.         if(simpleInterfaceProfileMenuList) {
  1873.             simpleInterfaceProfileMenuList.selectedIndex = this.getProfileIndex();
  1874.         }
  1875.         
  1876.         // The profile site rule textbox:
  1877.         this.initProfileSiteRuleTextBox();
  1878.     },
  1879.  
  1880.     getGlobalProfileIndex: function() {
  1881.         if(this.globalProfileIndex == null) {
  1882.             this.globalProfileIndex = this.autofillFormsPrefs.getIntPref('globalProfileIndex');
  1883.         }
  1884.         return this.globalProfileIndex;
  1885.     },
  1886.     
  1887.     setGlobalProfileIndex: function(index) {
  1888.         if(this.globalProfileIndex == index) {
  1889.             return;
  1890.         }
  1891.         this.autofillFormsPrefs.setIntPref('globalProfileIndex',parseInt(index));
  1892.         // The settings page doesn't observe preferences changes - set the profileIndex manually:
  1893.         this.globalProfileIndex = index;
  1894.     },
  1895.  
  1896.     getFormFieldsContextMenuProfileIndex: function() {
  1897.         if(this.formFieldsContextMenuProfileIndex == null) {
  1898.             this.formFieldsContextMenuProfileIndex
  1899.                 = this.autofillFormsPrefs.getIntPref('formFieldsContextMenuProfileIndex');
  1900.         }
  1901.         return this.formFieldsContextMenuProfileIndex;
  1902.     },
  1903.     
  1904.     setFormFieldsContextMenuProfileIndex: function(index) {
  1905.         if(this.formFieldsContextMenuProfileIndex == index) {
  1906.             return;
  1907.         }
  1908.         this.autofillFormsPrefs.setIntPref('formFieldsContextMenuProfileIndex',parseInt(index));
  1909.         // The settings page doesn't observe preferences changes - set the profileIndex manually:
  1910.         this.formFieldsContextMenuProfileIndex = index;
  1911.     },
  1912.  
  1913.     getProfileLabelsFile: function() {
  1914.         var file = this.getConfigDirectory();
  1915.         file.append('profileLabels.txt');
  1916.         if(!file.exists()) {
  1917.             file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0660);
  1918.         }
  1919.         return file;
  1920.     },
  1921.  
  1922.     exportProfileLabelsToConfigDirectory: function() {
  1923.         var prefString;
  1924.         // Get the profileLabels string from the preferences:
  1925.         prefString = this.autofillFormsPrefs
  1926.                             .getComplexValue('profileLabels',Components.interfaces.nsIPrefLocalizedString)
  1927.                             .data;
  1928.         if(prefString) {
  1929.             this.setFileContent(this.getProfileLabelsFile(), prefString);
  1930.         }
  1931.     },
  1932.     
  1933.     importProfileLabelsFromConfigDirectory: function() {
  1934.         var prefString;
  1935.         prefString = this.getFileContent(this.getProfileLabelsFile());
  1936.         if(prefString) {
  1937.             // Store the profileLabels as unicode string in the preferences:
  1938.             this.autofillFormsPrefs.setComplexValue(
  1939.                 'profileLabels',
  1940.                 Components.interfaces.nsISupportsString,
  1941.                 this.getUnicodeString(prefString)
  1942.             );
  1943.         }
  1944.     },
  1945.     
  1946.     getProfileLabels: function() {
  1947.         if(this.profileLabels == null) {
  1948.             var prefString;
  1949.             if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  1950.                 // Get the profileLabels string from the profileLabels file in the configDirectory:
  1951.                 prefString = this.getFileContent(this.getProfileLabelsFile());
  1952.             }
  1953.             if(!prefString) {
  1954.                 prefString = this.autofillFormsPrefs
  1955.                                 .getComplexValue('profileLabels',Components.interfaces.nsIPrefLocalizedString)
  1956.                                 .data;
  1957.             }
  1958.             // The profile labels are stored as a string with tabs as separators:
  1959.             this.profileLabels = prefString.split('\t');
  1960.         }
  1961.         return this.profileLabels;
  1962.     },
  1963.     
  1964.     setProfileLabels: function(profileLabels) {
  1965.         // Save the profile labels separated by tabs:
  1966.         var prefString = profileLabels.join('\t');
  1967.         if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  1968.             this.setFileContent(this.getProfileLabelsFile(), prefString);
  1969.         } else {
  1970.             this.autofillFormsPrefs.setComplexValue(
  1971.                 'profileLabels',
  1972.                 Components.interfaces.nsISupportsString,
  1973.                 this.getUnicodeString(prefString)
  1974.             );
  1975.         }
  1976.     },
  1977.  
  1978.     getProfileLabel: function(index) {
  1979.         while(this.getProfileLabels().length <= index) {
  1980.             this.getProfileLabels().push(this.getUniqueProfileLabel());
  1981.         }
  1982.         return this.getProfileLabels()[index];
  1983.     },
  1984.     
  1985.     setProfileLabel: function(index, label) {
  1986.         while(this.getProfileLabels().length <= index) {
  1987.             this.getProfileLabels().push(this.getUniqueProfileLabel());
  1988.         }
  1989.         this.getProfileLabels()[index] = label;
  1990.         // Save the profileLabels list in the preferences:
  1991.         this.setProfileLabels(this.getProfileLabels());
  1992.     },
  1993.     
  1994.     getUniqueProfileLabel: function(profileLabel) {
  1995.         if(!profileLabel) {
  1996.             profileLabel = 'Profile';
  1997.         }
  1998.         
  1999.         // Make sure the profile label is unique:
  2000.         if(!this.inArray(this.getProfileLabels(), profileLabel)) {
  2001.             return profileLabel;
  2002.         }
  2003.         var i = profileLabel.lastIndexOf(' ');
  2004.         var n = parseInt(profileLabel.substr(i+2));
  2005.         if(isNaN(n)) {
  2006.             return this.getUniqueProfileLabel(profileLabel+' (2)');
  2007.         }
  2008.         n++;
  2009.         profileLabel = profileLabel.substr(0, i)+' ('+n+')';
  2010.         return this.getUniqueProfileLabel(profileLabel);
  2011.     },
  2012.     
  2013.     changeProfileLabel: function(newProfileLabel) {
  2014.         var profilesMenuList = document.getElementById('profilesMenuList');
  2015.         if(profilesMenuList) {
  2016.             // Make sure the new profile label is safe and unique:
  2017.             newProfileLabel = this.getUniqueProfileLabel(this.makeSafe(newProfileLabel));
  2018.             // Update the label of the selected profile:
  2019.             this.setProfileLabel(this.getProfileIndex(), newProfileLabel);
  2020.             // Update the profiles textbox contents:
  2021.             profilesMenuList.inputField.value = newProfileLabel;
  2022.             document.getElementById('profileLabelTextBox').value = newProfileLabel;
  2023.             // Update the profiles lists:
  2024.             this.updateProfilesLists();
  2025.         }
  2026.     },
  2027.  
  2028.     initProfileSiteRuleTextBox: function(event) {
  2029.         var profileSiteRuleTextBox = document.getElementById('profileSiteRuleTextBox');
  2030.         if(profileSiteRuleTextBox) {
  2031.             profileSiteRuleTextBox.value = this.getProfileSiteRule(this.getProfileIndex());
  2032.         }
  2033.     },
  2034.     
  2035.     changeProfileSiteRule: function(siteRule) {
  2036.         var profileSiteRuleTextBox = document.getElementById('profileSiteRuleTextBox');
  2037.         if(profileSiteRuleTextBox) {
  2038.             // Check the regular expression before updating the profile site rules:
  2039.             try {
  2040.                 siteRule = this.getRegExpStr(
  2041.                     this.makeSafe(siteRule)
  2042.                 );
  2043.                 profileSiteRuleTextBox.value = siteRule;
  2044.                 document.getElementById('profileSiteRuleTextBox2').value = siteRule;
  2045.                 
  2046.                 var newProfileSiteRules = this.getProfileSiteRules();
  2047.                 newProfileSiteRules[this.getProfileIndex()] = siteRule;
  2048.                 this.setProfileSiteRules(newProfileSiteRules);
  2049.                 
  2050.                 // Update the profiles tree:
  2051.                 if(this.profilesTreeBox) {
  2052.                     this.profilesTreeBox.invalidateRow(this.getProfileIndex());
  2053.                 }
  2054.             } catch(e) {
  2055.                 this.invalidRegExpAlert(e);
  2056.             }            
  2057.         }
  2058.     },
  2059.  
  2060.     getProfileSiteRulesFile: function() {
  2061.         var file = this.getConfigDirectory();
  2062.         file.append('profileSiteRules.txt');
  2063.         if(!file.exists()) {
  2064.             file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0660);
  2065.         }
  2066.         return file;
  2067.     },
  2068.  
  2069.     exportProfileSiteRulesToConfigDirectory: function() {
  2070.         var prefString;
  2071.         // Get the profileSiteRules string from the preferences:
  2072.         prefString = this.autofillFormsPrefs
  2073.                             .getComplexValue('profileSiteRules',Components.interfaces.nsISupportsString)
  2074.                             .data;
  2075.         if(prefString) {
  2076.             this.setFileContent(this.getProfileSiteRulesFile(), prefString);
  2077.         }
  2078.     },
  2079.     
  2080.     importProfileSiteRulesFromConfigDirectory: function() {
  2081.         var prefString;
  2082.         prefString = this.getFileContent(this.getProfileSiteRulesFile());
  2083.         if(prefString) {
  2084.             // Store the profileSiteRules as unicode string in the preferences:
  2085.             this.autofillFormsPrefs.setComplexValue(
  2086.                 'profileSiteRules',
  2087.                 Components.interfaces.nsISupportsString,
  2088.                 this.getUnicodeString(prefString)
  2089.             );
  2090.         }
  2091.     },
  2092.     
  2093.     getProfileSiteRules: function() {
  2094.         if(this.profileSiteRules == null) {
  2095.             var prefString;
  2096.             if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  2097.                 // Get the profileSiteRules string from the profileSiteRules file in the configDirectory:
  2098.                 prefString = this.getFileContent(this.getProfileSiteRulesFile());
  2099.             }
  2100.             if(!prefString) {
  2101.                 prefString = this.autofillFormsPrefs
  2102.                                 .getComplexValue('profileSiteRules',Components.interfaces.nsISupportsString)
  2103.                                 .data;
  2104.             }
  2105.             // The profile SiteRules are stored as a string with tabs as separators:
  2106.             this.profileSiteRules = prefString.split('\t');
  2107.         }
  2108.         return this.profileSiteRules;
  2109.     },
  2110.     
  2111.     setProfileSiteRules: function(profileSiteRules) {
  2112.         // Save the profile SiteRules separated by tabs:
  2113.         var prefString = profileSiteRules.join('\t');
  2114.         if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  2115.             this.setFileContent(this.getProfileSiteRulesFile(), prefString);
  2116.         } else {
  2117.             this.autofillFormsPrefs.setComplexValue(
  2118.                 'profileSiteRules',
  2119.                 Components.interfaces.nsISupportsString,
  2120.                 this.getUnicodeString(prefString)
  2121.             );
  2122.         }
  2123.     },
  2124.  
  2125.     getProfileSiteRule: function(index) {
  2126.         while(this.getProfileSiteRules().length <= index) {
  2127.             this.getProfileSiteRules().push('(?:)');
  2128.         }
  2129.         return this.getProfileSiteRules()[index];
  2130.     },
  2131.  
  2132.     setProfileSiteRule: function(index, siteRule) {
  2133.         while(this.getProfileSiteRules().length <= index) {
  2134.             this.getProfileSiteRules().push('(?:)');
  2135.         }
  2136.         this.getProfileSiteRules()[index] = siteRule;
  2137.         // Save the profileSiteRules in the preferences:
  2138.         this.setProfileSiteRules(this.getProfileSiteRules());
  2139.     },
  2140.  
  2141.     addFormAsProfile: function(event) {
  2142.         if(this.targetFormField && this.targetFormField.form) {        
  2143.             var elements = this.targetFormField.form.elements;
  2144.             var doc = this.targetFormField.form.ownerDocument;
  2145.  
  2146.             var newProfile = new Array();
  2147.             
  2148.             // Go through the form elements:
  2149.             for(var i=0; i<elements.length; i++) {            
  2150.                 // Only use valid form fields:
  2151.                 if(this.isValidFormField(elements[i])) {
  2152.                     var value;
  2153.                     var overwrite = true;
  2154.  
  2155.                     // Create the fieldRule (from name, label or id):
  2156.                     var fieldRule = this.getFieldRuleForElement(elements[i]);
  2157.                     
  2158.                     switch(elements[i].type) {
  2159.                         case 'checkbox':
  2160.                             // Add a rule to uncheck the checkbox if it is unchecked:
  2161.                             if(!elements[i].checked) {
  2162.                                 overwrite = false;
  2163.                             }
  2164.                             value = this.getRegExpStrForValue(elements[i].value);
  2165.                             break;
  2166.                         case 'radio':
  2167.                             // Only add checked radio buttons:
  2168.                             if(!elements[i].checked) {
  2169.                                 continue;
  2170.                             }
  2171.                             value = this.getRegExpStrForValue(elements[i].value);
  2172.                             break;
  2173.                         case 'select-one':
  2174.                             value = this.getRegExpStrForValue(elements[i].value);
  2175.                             break;
  2176.                         case 'select-multiple':
  2177.                             var fieldRuleLabel = this.makeSafe(this.getFieldRuleNameForElement(elements[i]));
  2178.                             // Add all options as fieldRules, set "overwrite" to true if selected:
  2179.                             for(var j = 0; j < elements[i].options.length; j++) {
  2180.                                 newProfile.push(
  2181.                                     this.createFieldRule(
  2182.                                         fieldRuleLabel+' ('+j+')',
  2183.                                         this.getRegExpStrForValue(elements[i].options[j].value),
  2184.                                         fieldRule,
  2185.                                         '(?:)',
  2186.                                         elements[i].options[j].selected,
  2187.                                         true
  2188.                                     )
  2189.                                 );
  2190.                             }
  2191.                             continue;
  2192.                         default:
  2193.                             value = this.makeSafe(this.replaceControlCharacters(elements[i].value));
  2194.                             break;
  2195.                     }
  2196.  
  2197.                     // Add the current element as new rule to the profile list:
  2198.                     newProfile.push(
  2199.                         this.createFieldRule(
  2200.                             this.makeSafe(this.getFieldRuleNameForElement(elements[i])),
  2201.                             value,
  2202.                             fieldRule,
  2203.                             '(?:)',
  2204.                             overwrite,
  2205.                             true
  2206.                         )
  2207.                     );
  2208.                 }
  2209.             }
  2210.  
  2211.             // Initialize the fieldRules:
  2212.             this.getFieldRules();
  2213.             
  2214.             // Add the new profile to the fieldRules:
  2215.             this.fieldRules.push(newProfile);
  2216.             // Save the profiles in the preferences:
  2217.             this.setFieldRules();
  2218.         
  2219.             // Add a label for default empty profile:
  2220.             if(this.getProfileLabels().length == 0) {
  2221.                 this.getProfileLabels().push(this.getUniqueProfileLabel());
  2222.             }        
  2223.             // Use the documents hostname as profile label and add it to the profile labels list:
  2224.             this.getProfileLabels().push(this.getUniqueProfileLabel(this.makeSafe(doc.location.host)));
  2225.             // Save the profileLabels list in the preferences:
  2226.             this.setProfileLabels(this.getProfileLabels());
  2227.  
  2228.             // Use the protocol and domain of the web form as profile siteRule:
  2229.             this.getProfileSiteRules().push(this.getSiteRuleForURL(doc.location.protocol+'//'+doc.location.host));
  2230.             // Save the profileSiteRules in the preferences:
  2231.             this.setProfileSiteRules(this.getProfileSiteRules());
  2232.             
  2233.             // Save the the new profile index as selected profileIndex:
  2234.             this.setProfileIndex(this.getProfileLabels().length-1);
  2235.  
  2236.             // Reset the target form field:
  2237.             this.targetFormField = null;
  2238.  
  2239.             // Create parameters for the settings page:
  2240.             var params = new Object();
  2241.             params.newProfileFromForm = true;
  2242.             
  2243.             // Open up the settings page:
  2244.             this.showDialog('chrome://autofillForms/content/autofillFormsOptions.xul', params);
  2245.         }
  2246.     },
  2247.     
  2248.     addProfile: function(newProfileLabel) {
  2249.         // Duplicate the selected profile (do a deep copy):
  2250.         this.fieldRules.push(
  2251.             this.copyFieldRules(this.getFieldRules())
  2252.         );
  2253.         // Save the profiles in the preferences:
  2254.         this.setFieldRules();
  2255.         // Add profile label for default empty profile:
  2256.         if(this.getProfileLabels().length == 0) {
  2257.             this.getProfileLabels().push(this.getUniqueProfileLabel());
  2258.         }
  2259.         // Add the (unique) newProfileLabel to the profileLabels list:
  2260.         this.getProfileLabels().push(this.getUniqueProfileLabel(this.makeSafe(newProfileLabel)));
  2261.         // Save the profileLabels list in the preferences:
  2262.         this.setProfileLabels(this.getProfileLabels());
  2263.         // Add a new empty profileSiteRule:
  2264.         this.getProfileSiteRules().push('(?:)');
  2265.         // Save the profileSiteRules in the preferences:
  2266.         this.setProfileSiteRules(this.getProfileSiteRules());
  2267.         // Save the the new profile index as selected profileIndex:
  2268.         this.setProfileIndex(this.getProfileLabels().length-1);
  2269.         // Update the profiles lists:
  2270.         this.initProfilesLists();
  2271.         // Re-init the fieldRules tree:
  2272.         this.initTree();
  2273.         // Re-initialize the simple interface:
  2274.         this.initSimpleInterface();
  2275.     },
  2276.     
  2277.     removeProfile: function(event) {
  2278.         if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
  2279.             // Confirmation dialog:
  2280.             if(!this.getPrompts().confirm(
  2281.                     null,
  2282.                     this.getStringBundle().getString('removeProfileTitle'),
  2283.                     this.getStringBundle().getString('removeProfileText')
  2284.                 )
  2285.             ) {
  2286.                 return;    
  2287.             }
  2288.         }
  2289.         
  2290.         // Remove the selected profile from the list:
  2291.         this.fieldRules.splice(this.getProfileIndex(),1);
  2292.         // Save the profiles in the preferences:
  2293.         this.setFieldRules();
  2294.         // Remove the selected profile from the profileLabels list:
  2295.         this.getProfileLabels().splice(this.getProfileIndex(),1);
  2296.         // Save the profileLabels list in the preferences:
  2297.         this.setProfileLabels(this.getProfileLabels());
  2298.         // Remove the selected profile's siteRule:
  2299.         this.getProfileSiteRules().splice(this.getProfileIndex(),1);
  2300.         // Save the profileSiteRules in the preferences:
  2301.         this.setProfileSiteRules(this.getProfileSiteRules());
  2302.         // Adjust the profileIndex if the last profile on the list has been deleted:
  2303.         if(this.getProfileIndex()+1 > this.fieldRules.length) {
  2304.             var newIndex = this.fieldRules.length>0 ? this.fieldRules.length-1 : 0;
  2305.             this.setProfileIndex(newIndex);
  2306.         }
  2307.         // Update the profiles lists:
  2308.         this.initProfilesLists();
  2309.         // Re-init the tree:
  2310.         this.initTree();
  2311.         // Re-initialize the simple interface:
  2312.         this.initSimpleInterface();
  2313.     },
  2314.     
  2315.     createFieldRule: function(name,value,fieldRule,siteRule,overwrite,enabled) {
  2316.         var rule = new Object();
  2317.         rule['fieldRuleName'] = name;
  2318.         rule['fieldRuleValue'] = value;
  2319.         rule['fieldRuleFieldRule'] = fieldRule;
  2320.         rule['fieldRuleSiteRule'] = siteRule;
  2321.         rule['fieldRuleOverwrite'] = overwrite;
  2322.         rule['fieldRuleEnabled'] = enabled;
  2323.         return rule;
  2324.     },
  2325.  
  2326.     getRegExpPasswordLabel: function() {
  2327.         if(!arguments.callee.regExpPass) {
  2328.             arguments.callee.regExpPass = new RegExp(
  2329.                                 this.autofillFormsPrefs
  2330.                                 .getComplexValue('regExpPasswordLabel',Components.interfaces.nsIPrefLocalizedString)
  2331.                                 .data,
  2332.                                 'i');
  2333.         }
  2334.         return arguments.callee.regExpPass;
  2335.     },
  2336.     
  2337.     initTree: function() {    
  2338.         // Get the tree:
  2339.         this.tree = document.getElementById('fieldRulesTree');
  2340.         
  2341.         if(this.tree) {
  2342.         
  2343.             // Implement the TreeView interface:
  2344.             this.treeView = {
  2345.                 rowCount: 0,
  2346.                 setTree: function(tree){},
  2347.                 getImageSrc: function(row,column) {},
  2348.                 getProgressMode: function(row,column) {},
  2349.                 getCellValue: function(row,column) {
  2350.                     var rowObj = this.parent.getFieldRules()[row];
  2351.                     if(rowObj) {
  2352.                         return rowObj[column.id];
  2353.                     }
  2354.                 },
  2355.                 getCellText: function(row,column){
  2356.                     var rowObj = this.parent.getFieldRules()[row];
  2357.                     if(rowObj) {
  2358.                         if(column.id=='fieldRuleValue' &&
  2359.                             this.parent.getRegExpPasswordLabel().test(rowObj['fieldRuleName'])) {
  2360.                             // Show passwords as asterisks:
  2361.                             return rowObj[column.id].replace(/./g, '*');
  2362.                         } else {
  2363.                             return rowObj[column.id];
  2364.                         }
  2365.                     }
  2366.                     return '';
  2367.                 },
  2368.                 isEditable: function(row,column){
  2369.                     // Only checkbox columns are editable:
  2370.                     if(column.id=='fieldRuleOverwrite' || column.id=='fieldRuleEnabled')
  2371.                         return true;
  2372.                     else
  2373.                         return false;
  2374.                 },
  2375.                 setCellValue: function(row,column,value){
  2376.                     var rowObj = this.parent.getFieldRules()[row];
  2377.                     if(rowObj) {
  2378.                         rowObj[column.id] = value;
  2379.                         // Notify the tree:
  2380.                         this.parent.treeBox.invalidateRow(row);
  2381.                         // Update the preferences:
  2382.                         this.parent.setFieldRules();
  2383.                         // Update the simple interface (add/remove enabled/disabled rules):
  2384.                         if(column.id=='fieldRuleEnabled') {
  2385.                             if(value == 'true') {
  2386.                                 this.parent.addSimpleInterfaceRow(row);
  2387.                             } else {
  2388.                                 this.parent.removeSimpleInterfaceRow(row);
  2389.                             }
  2390.                         }    
  2391.                     }
  2392.                 },
  2393.                 isSeparator: function(index) {return false;},
  2394.                 isSorted: function() {return false;},
  2395.                 isContainer: function(index) {return false;},
  2396.                 cycleHeader: function(column) {},
  2397.                 getRowProperties: function(row,prop){},
  2398.                 getColumnProperties: function(column,prop){},
  2399.                 getCellProperties: function(row,column,prop){},
  2400.                 getParentIndex: function(index) {return -1}
  2401.             };
  2402.             // Set the autofillForms object as parent:
  2403.             this.treeView.parent = this;
  2404.             
  2405.             // Set the tree length using the fieldRules list length:
  2406.             this.treeView.rowCount = this.getFieldRules().length;
  2407.                     
  2408.             // Assign the treeview:
  2409.             this.tree.view = this.treeView;
  2410.             
  2411.             // The TreeSelection object:
  2412.             this.selection = this.tree.view.selection;
  2413.             
  2414.             // The TreeBox object:
  2415.             this.treeBox = this.tree.treeBoxObject;        
  2416.         }
  2417.     },
  2418.         
  2419.     sortFieldRules: function(event) {
  2420.         // See method selectedFieldRule() why this has to be set to null:
  2421.         this.lastSelectedIndex = null;
  2422.  
  2423.         if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
  2424.             // Confirmation dialog:
  2425.             if(!this.getPrompts().confirm(
  2426.                     null,
  2427.                     this.getStringBundle().getString('sortFieldRulesTitle'),
  2428.                     this.getStringBundle().getString('sortFieldRulesText')
  2429.                 )
  2430.             ) {
  2431.                 return;    
  2432.             }
  2433.         }    
  2434.  
  2435.         // Get the id of the column:
  2436.         var id = event.target.id;
  2437.         
  2438.         // Helper function to sort the fieldRules objects:
  2439.         function customSort(a,b) {
  2440.             // This enables comparison of boolean true and false:
  2441.             var x = a[id].toString();
  2442.             var y = b[id].toString();
  2443.  
  2444.             if(x > y) return 1;
  2445.             if(x < y) return -1;
  2446.             return 0;
  2447.         }
  2448.         
  2449.         // Sort the form field rules using the helper function:
  2450.         this.getFieldRules().sort(customSort);
  2451.         
  2452.         // Change sort direction for next click:
  2453.         if(this.ascending) {
  2454.             this.ascending = false;
  2455.         } else {
  2456.             this.getFieldRules().reverse();
  2457.             this.ascending = true;
  2458.         }
  2459.         
  2460.         // Notify the tree:
  2461.         this.treeBox.invalidate();
  2462.         
  2463.         // Clear out selections
  2464.         this.selection.select(-1);
  2465.         
  2466.         // Update the preferences:
  2467.         this.setFieldRules();
  2468.  
  2469.         // Re-initialize the simple interface:
  2470.         this.initSimpleInterface();
  2471.     },
  2472.     
  2473.     selectedFieldRule: function(event) {
  2474.         if(this.selection.currentIndex == -1) {
  2475.             // Disable buttons:
  2476.             document.getElementById('buttonRemoveFieldRule').setAttribute('disabled', 'true');
  2477.             document.getElementById('buttonMoveUpFieldRule').setAttribute('disabled', 'true');
  2478.             document.getElementById('buttonMoveDownFieldRule').setAttribute('disabled', 'true');
  2479.             
  2480.             this.lastSelectedIndex = null;
  2481.         } else if(this.selection.count == 1) {
  2482.             // The onchange event (as well as onblur, etc.) of the textboxes seems to be ignored if a new item is selected,
  2483.             // so we try and apply the field rules of the last element (if changed):
  2484.             if(this.lastSelectedIndex !== null) {
  2485.                 this.applyFieldRuleOnIndex(this.lastSelectedIndex);
  2486.             }
  2487.             
  2488.             // Update the textboxes with the selected fieldRule:
  2489.             var index = this.selection.currentIndex;
  2490.             document.getElementById('fieldRuleNameTextBox').value = this.getFieldRules()[index]['fieldRuleName'];
  2491.             document.getElementById('fieldRuleValueTextBox').value = this.getFieldRules()[index]['fieldRuleValue'];
  2492.             document.getElementById('fieldRuleFieldRuleTextBox').value = this.getFieldRules()[index]['fieldRuleFieldRule'];
  2493.             document.getElementById('fieldRuleSiteRuleTextBox').value = this.getFieldRules()[index]['fieldRuleSiteRule'];
  2494.             
  2495.             // Enable/Disable buttons:
  2496.             document.getElementById('buttonRemoveFieldRule').setAttribute('disabled', 'false');
  2497.             document.getElementById('buttonMoveUpFieldRule').setAttribute(
  2498.                 'disabled',
  2499.                 (index == 0)
  2500.             );
  2501.             document.getElementById('buttonMoveDownFieldRule').setAttribute(
  2502.                 'disabled',
  2503.                 (index == this.getFieldRules().length-1)
  2504.             );
  2505.             
  2506.             // Save the last selected index and reset it to null for any other action than just a single selection:
  2507.             this.lastSelectedIndex = index;
  2508.         } else if(this.selection.count > 1) {
  2509.             // Enable/Disable buttons:
  2510.             document.getElementById('buttonRemoveFieldRule').setAttribute('disabled', 'false');
  2511.             document.getElementById('buttonMoveUpFieldRule').setAttribute('disabled', 'true');
  2512.             document.getElementById('buttonMoveDownFieldRule').setAttribute('disabled', 'true');
  2513.             
  2514.             this.lastSelectedIndex = null;
  2515.         }
  2516.     },
  2517.  
  2518.     initProfilesTree: function() {
  2519.         this.profilesTree = document.getElementById('profilesTree');
  2520.         if(this.profilesTree) {
  2521.  
  2522.             // Implement the profiles TreeView interface:
  2523.             this.profilesTreeView = {
  2524.                 rowCount: 0,
  2525.                 setTree: function(tree){},
  2526.                 getImageSrc: function(row,column) {},
  2527.                 getProgressMode: function(row,column) {},
  2528.                 getCellValue: function(row,column) {
  2529.                     if(column.id=='profilesTreeColName') {
  2530.                         return this.parent.getProfileLabel(row);
  2531.                     } else {
  2532.                         return this.parent.getProfileSiteRule(row);
  2533.                     }
  2534.                 },
  2535.                 getCellText: function(row,column){
  2536.                     if(column.id=='profilesTreeColName') {
  2537.                         return this.parent.getProfileLabel(row);
  2538.                     } else {
  2539.                         return this.parent.getProfileSiteRule(row);
  2540.                     }
  2541.                 },
  2542.                 isEditable: function(row,column){return false;},
  2543.                 setCellValue: function(row,column,value){},
  2544.                 isSeparator: function(index) {return false;},
  2545.                 isSorted: function() {return false;},
  2546.                 isContainer: function(index) {return false;},
  2547.                 cycleHeader: function(column) {},
  2548.                 getRowProperties: function(row,prop){},
  2549.                 getColumnProperties: function(column,prop){},
  2550.                 getCellProperties: function(row,column,prop){},
  2551.                 getParentIndex: function(index) {return -1}
  2552.             };
  2553.             // Set the autofillForms object as parent:
  2554.             this.profilesTreeView.parent = this;
  2555.  
  2556.             // Seems like we need to reset these arrays to have a consistens UI:
  2557.             this.profileLabels = null;
  2558.             this.profileSiteRules = null;
  2559.             
  2560.             // Set the tree length using the profiles labels list length:
  2561.             this.profilesTreeView.rowCount = this.getProfileLabels().length;
  2562.                     
  2563.             // Assign the treeview:
  2564.             this.profilesTree.view = this.profilesTreeView;
  2565.             
  2566.             // The TreeSelection object:
  2567.             this.profilesSelection = this.profilesTree.view.selection;
  2568.             
  2569.             // The TreeBox object:
  2570.             this.profilesTreeBox = this.profilesTree.treeBoxObject;
  2571.     
  2572.             // Select the current profile:
  2573.             this.profilesSelection.select(this.getProfileIndex());
  2574.     
  2575.             // Ensure row is visible (scrolls if not):
  2576.             this.profilesTreeBox.ensureRowIsVisible(this.getProfileIndex());
  2577.         }
  2578.     },
  2579.     
  2580.     selectedProfile: function(event) {
  2581.         var index = this.profilesSelection.currentIndex;
  2582.         if(index != -1) {
  2583.             this.setProfileIndex(index);
  2584.             
  2585.             if(index > 0) {
  2586.                 document.getElementById('buttonMoveUpProfile').disabled = false;
  2587.             } else {
  2588.                 document.getElementById('buttonMoveUpProfile').disabled = true;
  2589.             }
  2590.             if(index+1 < this.getProfileLabels().length) {
  2591.                 document.getElementById('buttonMoveDownProfile').disabled = false;
  2592.             } else {
  2593.                 document.getElementById('buttonMoveDownProfile').disabled = true;
  2594.             }
  2595.             
  2596.             if(document.getElementById('profileLabelTextBox')) {
  2597.                 document.getElementById('profileLabelTextBox').value = this.getProfileLabel(this.getProfileIndex());
  2598.             }
  2599.             if(document.getElementById('profileSiteRuleTextBox2')) {
  2600.                 document.getElementById('profileSiteRuleTextBox2').value = this.getProfileSiteRule(this.getProfileIndex());
  2601.             }
  2602.         } else {
  2603.             document.getElementById('buttonMoveUpProfile').disabled = true;
  2604.             document.getElementById('buttonMoveDownProfile').disabled = true;
  2605.         }
  2606.     },
  2607.     
  2608.     moveUpProfile: function(event) {
  2609.         var tmpProfile = this.getFieldRules(this.getProfileIndex()-1);
  2610.         this.fieldRules[this.getProfileIndex()-1] = this.getFieldRules(this.getProfileIndex());
  2611.         this.fieldRules[this.getProfileIndex()] = tmpProfile;
  2612.         this.setFieldRules();
  2613.         
  2614.         var tmpProfileLabel = this.getProfileLabel(this.getProfileIndex()-1);
  2615.         this.getProfileLabels()[this.getProfileIndex()-1] = this.getProfileLabel(this.getProfileIndex());
  2616.         this.getProfileLabels()[this.getProfileIndex()] = tmpProfileLabel;
  2617.         this.setProfileLabels(this.getProfileLabels());
  2618.         
  2619.         var tmpProfileSiteRule = this.getProfileSiteRule(this.getProfileIndex()-1);
  2620.         this.getProfileSiteRules()[this.getProfileIndex()-1] = this.getProfileSiteRule(this.getProfileIndex());
  2621.         this.getProfileSiteRules()[this.getProfileIndex()] = tmpProfileSiteRule;
  2622.         this.setProfileSiteRules(this.getProfileSiteRules());
  2623.  
  2624.         this.setProfileIndex(this.getProfileIndex()-1);
  2625.         
  2626.         this.initProfilesLists();
  2627.     },
  2628.     
  2629.     moveDownProfile: function(event) {
  2630.         var tmpProfile = this.getFieldRules(this.getProfileIndex()+1);
  2631.         this.fieldRules[this.getProfileIndex()+1] = this.getFieldRules(this.getProfileIndex());
  2632.         this.fieldRules[this.getProfileIndex()] = tmpProfile;
  2633.         this.setFieldRules();
  2634.         
  2635.         var tmpProfileLabel = this.getProfileLabel(this.getProfileIndex()+1);
  2636.         this.getProfileLabels()[this.getProfileIndex()+1] = this.getProfileLabel(this.getProfileIndex());
  2637.         this.getProfileLabels()[this.getProfileIndex()] = tmpProfileLabel;
  2638.         this.setProfileLabels(this.getProfileLabels());
  2639.         
  2640.         var tmpProfileSiteRule = this.getProfileSiteRule(this.getProfileIndex()+1);
  2641.         this.getProfileSiteRules()[this.getProfileIndex()+1] = this.getProfileSiteRule(this.getProfileIndex());
  2642.         this.getProfileSiteRules()[this.getProfileIndex()] = tmpProfileSiteRule;
  2643.         this.setProfileSiteRules(this.getProfileSiteRules());
  2644.  
  2645.         this.setProfileIndex(this.getProfileIndex()+1);
  2646.         
  2647.         this.initProfilesLists();
  2648.     },
  2649.     
  2650.     sortProfiles: function(event) {
  2651.         var obj = new Object();
  2652.         var newSelectedIndex = this.getProfileIndex();
  2653.         var tmpProfile;
  2654.         var oldIndex;
  2655.  
  2656.         switch(event.target.id) {
  2657.             case 'profilesTreeColName':
  2658.                 for(var i=0; i<this.getProfileLabels().length; i++) {
  2659.                     obj[this.getProfileLabel(i)] = i;
  2660.                 }
  2661.                 this.getProfileLabels().sort();
  2662.                 // Change sort direction for next click:
  2663.                 if(this.profilesAscending) {
  2664.                     this.profilesAscending = false;
  2665.                 } else {
  2666.                     this.getProfileLabels().reverse();
  2667.                     this.profilesAscending = true;
  2668.                 }
  2669.                 for(var i=0; i<this.getProfileLabels().length; i++) {
  2670.                     oldIndex = obj[this.getProfileLabel(i)];
  2671.                     if(oldIndex == i) {
  2672.                         continue;
  2673.                     }
  2674.                     if(oldIndex == this.getProfileIndex()) {
  2675.                         newSelectedIndex = i;
  2676.                     }
  2677.                     tmpProfile = this.getFieldRules(i);
  2678.                     this.fieldRules[i] = this.getFieldRules(oldIndex);
  2679.                     this.fieldRules[oldIndex] = tmpProfile;
  2680.                 }
  2681.                 break;
  2682.                 
  2683.             case 'profilesTreeColSiteRule':
  2684.                 for(var i=0; i<this.getProfileSiteRules().length; i++) {
  2685.                     obj[this.getProfileSiteRule(i)] = i;
  2686.                 }
  2687.                 this.getProfileSiteRules().sort();
  2688.                 // Change sort direction for next click:
  2689.                 if(this.profilesAscending) {
  2690.                     this.profilesAscending = false;
  2691.                 } else {
  2692.                     this.getProfileSiteRules().reverse();
  2693.                     this.profilesAscending = true;
  2694.                 }
  2695.                 for(var i=0; i<this.getProfileSiteRules().length; i++) {
  2696.                     oldIndex = obj[this.getProfileSiteRule(i)];
  2697.                     if(oldIndex == i) {
  2698.                         continue;
  2699.                     }
  2700.                     if(oldIndex == this.getProfileIndex()) {
  2701.                         newSelectedIndex = i;
  2702.                     }
  2703.                     tmpProfile = this.getFieldRules(i);
  2704.                     this.fieldRules[i] = this.getFieldRules(oldIndex);
  2705.                     this.fieldRules[oldIndex] = tmpProfile;
  2706.                 }
  2707.                 break;
  2708.         }
  2709.         
  2710.         this.setFieldRules();
  2711.         this.setProfileLabels(this.getProfileLabels());    
  2712.         this.setProfileSiteRules(this.getProfileSiteRules());
  2713.         
  2714.         this.setProfileIndex(newSelectedIndex);
  2715.         
  2716.         this.initProfilesLists();
  2717.     },
  2718.  
  2719.     profilesTreeHandleKeyPress: function(event) {
  2720.         if(event.keyCode == 46) {
  2721.             this.removeProfile();
  2722.         }
  2723.     },
  2724.     
  2725.     invalidRegExpAlert: function(error) {
  2726.         // Invalid regular expression alert:
  2727.         this.getPrompts().alert(
  2728.             null,
  2729.             this.getStringBundle().getString('invalidRegExpTitle'),
  2730.             this.getStringBundle().getString('invalidRegExpText') + "\n\n" + error
  2731.         );
  2732.     },
  2733.     
  2734.     makeSafe: function(str) {
  2735.         // Remove all tabs and linefeeds from the given string
  2736.         // (these are used as separators):
  2737.         return str.replace(/\t|\n/g, '');
  2738.     },
  2739.  
  2740.     replaceControlCharacters: function(str) {
  2741.         return str.replace(
  2742.             /\n|\t/g,
  2743.             this.replaceControlCharactersCallback
  2744.         );
  2745.     },
  2746.     
  2747.     replaceControlCharactersCallback: function(str) {
  2748.         switch(str) {
  2749.             case "\n":
  2750.                 return autofillForms.autofillFormsPrefs.getCharPref('placeholderLineBreak');
  2751.             case "\t":
  2752.                 return '    ';
  2753.             default:
  2754.                 return str;
  2755.         }
  2756.     },
  2757.  
  2758.     replaceControlCharacterPlaceholders: function(str) {
  2759.         try {
  2760.             var regExpObj = new RegExp(
  2761.                 '('
  2762.                 +this.autofillFormsPrefs.getCharPref('placeholderLineBreak')
  2763.                 +')|('
  2764.                 +this.autofillFormsPrefs.getCharPref('placeholderTab')
  2765.                 +')',
  2766.                 'g'
  2767.             );
  2768.             return str.replace(
  2769.                 regExpObj,
  2770.                 this.replaceControlCharacterPlaceholdersCallback
  2771.             );
  2772.         } catch(e) {
  2773.             return str;
  2774.         }
  2775.     },
  2776.     
  2777.     replaceControlCharacterPlaceholdersCallback: function(str) {
  2778.         switch(str) {
  2779.             case autofillForms.autofillFormsPrefs.getCharPref('placeholderLineBreak'):
  2780.                 return "\n";
  2781.             case autofillForms.autofillFormsPrefs.getCharPref('placeholderTab'):
  2782.                 return "\t";
  2783.             default:
  2784.                 return str;
  2785.         }
  2786.     },
  2787.  
  2788.     applyFieldRuleOnIndex: function(index) {
  2789.         // Check the regular expressions:
  2790.         try {
  2791.             var fieldRule = this.getRegExpStr(
  2792.                 this.makeSafe(document.getElementById('fieldRuleFieldRuleTextBox').value)
  2793.             );
  2794.             document.getElementById('fieldRuleFieldRuleTextBox').value = fieldRule;
  2795.             
  2796.             var siteRule = this.getRegExpStr(
  2797.                 this.makeSafe(document.getElementById('fieldRuleSiteRuleTextBox').value)
  2798.             );
  2799.             document.getElementById('fieldRuleSiteRuleTextBox').value = siteRule;
  2800.         } catch(e) {
  2801.             this.invalidRegExpAlert(e);
  2802.             return;
  2803.         }
  2804.  
  2805.         var ruleName = this.makeSafe(document.getElementById('fieldRuleNameTextBox').value);
  2806.         var ruleValue = this.makeSafe(document.getElementById('fieldRuleValueTextBox').value);
  2807.  
  2808.         if(    this.getFieldRules()[index] && (
  2809.             this.getFieldRules()[index]['fieldRuleName'] != ruleName ||
  2810.             this.getFieldRules()[index]['fieldRuleValue'] != ruleValue ||
  2811.             this.getFieldRules()[index]['fieldRuleFieldRule'] != fieldRule ||
  2812.             this.getFieldRules()[index]['fieldRuleSiteRule'] != siteRule)) {
  2813.             // Update the formFieldRule on the given index:
  2814.             this.getFieldRules()[index]['fieldRuleName'] = ruleName;
  2815.             this.getFieldRules()[index]['fieldRuleValue'] = ruleValue;
  2816.             this.getFieldRules()[index]['fieldRuleFieldRule'] = fieldRule;
  2817.             this.getFieldRules()[index]['fieldRuleSiteRule'] = siteRule;
  2818.     
  2819.             // Notify the tree:
  2820.             this.treeBox.invalidateRow(index);
  2821.             
  2822.             // Update the preferences:
  2823.             this.setFieldRules();    
  2824.             
  2825.             // Update the related row of the simple interface:
  2826.             this.updateSimpleInterfaceRow(index);
  2827.         }
  2828.     },
  2829.     
  2830.     applyFieldRule: function(event) {
  2831.         // Only apply changes if one item is selected:
  2832.         if(this.selection.count == 1) {
  2833.             // Update the selected formFieldRule:
  2834.             this.applyFieldRuleOnIndex(this.selection.currentIndex);
  2835.         }
  2836.     },
  2837.     
  2838.     addFieldRule: function(event) {
  2839.         // See method selectedFieldRule() why this has to be set to null:
  2840.         this.lastSelectedIndex = null;
  2841.         
  2842.         // Check the regular expressions:
  2843.         try {
  2844.             var fieldRuleFieldRuleTextBox = document.getElementById('fieldRuleFieldRuleTextBox');
  2845.             var fieldRule = this.getRegExpStr(
  2846.                 this.makeSafe(fieldRuleFieldRuleTextBox.value)
  2847.             );
  2848.             fieldRuleFieldRuleTextBox.value = fieldRule;
  2849.  
  2850.             var fieldRuleSiteRuleTextBox = document.getElementById('fieldRuleSiteRuleTextBox');
  2851.             var siteRule = this.getRegExpStr(
  2852.                 this.makeSafe(fieldRuleSiteRuleTextBox.value)
  2853.             );
  2854.             fieldRuleSiteRuleTextBox.value = siteRule;
  2855.         } catch(e) {
  2856.             this.invalidRegExpAlert(e);
  2857.             return;
  2858.         }
  2859.         
  2860.         var newFieldRule =     this.createFieldRule(
  2861.             this.makeSafe(document.getElementById('fieldRuleNameTextBox').value),
  2862.             this.makeSafe(document.getElementById('fieldRuleValueTextBox').value),
  2863.             fieldRule,
  2864.             siteRule,
  2865.             true,
  2866.             true
  2867.         )
  2868.         
  2869.         var newFieldRuleIndex;
  2870.         
  2871.         // Add the new formFieldRule right after the selected position or to the start of the list:
  2872.         if(this.selection.currentIndex == -1 || this.selection.currentIndex == this.treeView.rowCount) {
  2873.             this.getFieldRules().unshift(newFieldRule);
  2874.             newFieldRuleIndex = 0;
  2875.         } else {
  2876.             newFieldRuleIndex = this.selection.currentIndex+1;
  2877.             this.getFieldRules().splice(
  2878.                 newFieldRuleIndex,
  2879.                 0,
  2880.                 newFieldRule
  2881.             );
  2882.         }
  2883.  
  2884.         // Update the tree count and notify the tree:
  2885.         this.treeView.rowCount++;
  2886.         this.treeBox.rowCountChanged(this.treeView.rowCount, +1);
  2887.         this.treeBox.invalidate();
  2888.         
  2889.         // Select the new item:
  2890.         this.selection.select(newFieldRuleIndex);
  2891.  
  2892.         // Ensure row is visible (scrolls if not):
  2893.         this.treeBox.ensureRowIsVisible(newFieldRuleIndex);
  2894.         
  2895.         // Update the preferences:
  2896.         this.setFieldRules();
  2897.  
  2898.         // Re-initialize the simple interface:
  2899.         this.initSimpleInterface();
  2900.     },
  2901.     
  2902.     removeFieldRule: function(event) {
  2903.         this.removeSelectedFieldRules();
  2904.     },
  2905.     
  2906.     moveUpFieldRule: function(event) {
  2907.         // See method selectedFieldRule() why this has to be set to null:
  2908.         this.lastSelectedIndex = null;
  2909.         
  2910.         var index = this.selection.currentIndex;
  2911.  
  2912.         // Change place with the next upper item:
  2913.         var sibling = this.getFieldRules()[index-1];
  2914.         this.getFieldRules()[index-1] = this.getFieldRules()[index];
  2915.         this.getFieldRules()[index] = sibling;
  2916.         
  2917.         // Keep moved item selected:
  2918.         this.selection.select(index-1);
  2919.         
  2920.         // Notify the tree:
  2921.         this.treeBox.invalidate();
  2922.         
  2923.         // Ensure row is visible (scrolls if not):
  2924.         this.treeBox.ensureRowIsVisible(index-1);
  2925.         
  2926.         // Update the preferences:
  2927.         this.setFieldRules();
  2928.  
  2929.         // Update the related rows of the simple interface:
  2930.         this.updateSimpleInterfaceRow(index-1);
  2931.         this.updateSimpleInterfaceRow(index);
  2932.     },
  2933.  
  2934.     moveDownFieldRule: function(event) {
  2935.         // See method selectedFieldRule() why this has to be set to null:
  2936.         this.lastSelectedIndex = null;
  2937.         
  2938.         var index = this.selection.currentIndex;
  2939.  
  2940.         // Change place with the next lower item:
  2941.         var sibling = this.getFieldRules()[index+1];
  2942.         this.getFieldRules()[index+1] = this.getFieldRules()[index];
  2943.         this.getFieldRules()[index] = sibling;
  2944.         
  2945.         // Keep moved item selected:
  2946.         this.selection.select(index+1);
  2947.         
  2948.         // Notify the tree:
  2949.         this.treeBox.invalidate();
  2950.  
  2951.         // Ensure row is visible (scrolls if not):
  2952.         this.treeBox.ensureRowIsVisible(index+1);
  2953.         
  2954.         // Update the preferences:
  2955.         this.setFieldRules();
  2956.  
  2957.         // Update the related rows of the simple interface:
  2958.         this.updateSimpleInterfaceRow(index+1);
  2959.         this.updateSimpleInterfaceRow(index);
  2960.     },
  2961.         
  2962.     removeSelectedFieldRules: function(event) {
  2963.         // See method selectedFieldRule() why this has to be set to null:
  2964.         this.lastSelectedIndex = null;
  2965.         
  2966.         if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
  2967.             // Confirmation dialog:
  2968.             if(!this.getPrompts().confirm(
  2969.                     null,
  2970.                     this.getStringBundle().getString('removeFieldRulesTitle'),
  2971.                     this.getStringBundle().getString('removeFieldRulesText')
  2972.                 )
  2973.             ) {
  2974.                 return;    
  2975.             }
  2976.         }
  2977.     
  2978.         // Start of update batch:
  2979.         this.treeBox.beginUpdateBatch();
  2980.             
  2981.         // Helper object to store a range:
  2982.         function Range(start, end) {
  2983.             this.start = start.value;
  2984.             this.end = end.value;
  2985.         }
  2986.         
  2987.         // List of ranges:
  2988.         var ranges = new Array();
  2989.         
  2990.         // Get the number of ranges:
  2991.         var numRanges = this.selection.getRangeCount();
  2992.         
  2993.         // Helper vars to store the range end points:
  2994.         var start = new Object();
  2995.         var end = new Object();
  2996.         
  2997.         // We store the list of ranges first, as calling
  2998.         // this.treeBox.rowCountChanged()
  2999.         // seems to invalidate the current selection
  3000.         
  3001.         for(var i=0; i < numRanges; i++) {
  3002.             // Get the current range end points:
  3003.             this.selection.getRangeAt(i,start,end);
  3004.             // Store them as a Range object in the ranges list:
  3005.             ranges[i] = new Range(start, end);
  3006.         }
  3007.         
  3008.         for(var i=0; i < numRanges; i++) {
  3009.             // Go through the stored ranges:
  3010.             for(var j = ranges[i].start; j <= ranges[i].end; j++) {
  3011.                 // Set the selected fieldRules to null:
  3012.                 this.getFieldRules()[j] = null;
  3013.             }
  3014.             
  3015.             // Calculate the new tree count:
  3016.             var count = ranges[i].end - ranges[i].start + 1;
  3017.             
  3018.             // Update the tree count and notify the tree:
  3019.             this.treeView.rowCount -= count;
  3020.             this.treeBox.rowCountChanged(ranges[i].start, -count);
  3021.         }
  3022.  
  3023.         // Collapse list by removing all the null entries
  3024.         for (var i=0; i < this.getFieldRules().length; i++) {
  3025.             if (!this.getFieldRules()[i]) {
  3026.                 var j = i;
  3027.                 while (j < this.getFieldRules().length && !this.getFieldRules()[j])
  3028.                     j++;
  3029.                 this.getFieldRules().splice(i, j-i);
  3030.             }
  3031.         }
  3032.         
  3033.         // Clear out selections
  3034.         this.selection.select(-1); 
  3035.         
  3036.         // End of update batch:
  3037.         this.treeBox.endUpdateBatch();    
  3038.         
  3039.         // Update the preferences:
  3040.         this.setFieldRules();
  3041.  
  3042.         // Re-initialize the simple interface:
  3043.         this.initSimpleInterface();
  3044.     },
  3045.  
  3046.     handleKeyPress: function(event) {
  3047.         if(event.keyCode == 46) {
  3048.             this.removeSelectedFieldRules();
  3049.         } else if(event.ctrlKey && event.which == 97) {
  3050.             if(this.tree && this.selection) {
  3051.                 try {
  3052.                     // Select all rows:
  3053.                     this.selection.selectAll();    
  3054.                 } catch(e) {
  3055.                     this.log(e);
  3056.                 }
  3057.             }
  3058.         }
  3059.     },
  3060.  
  3061.     getGlobalFieldRules: function() {        
  3062.         // Return the fieldRules for the selected global profile if globalProfileIndex is not out of range:
  3063.         if(this.getGlobalProfileIndex() >= 0 && this.getGlobalProfileIndex() < this.getProfileLabels().length) {
  3064.             return this.getFieldRules(this.getGlobalProfileIndex());
  3065.         } else {
  3066.             this.globalProfileIndex = 0;
  3067.             return this.getFieldRules(0);
  3068.         }        
  3069.     },
  3070.  
  3071.     getFileContent: function(file) {
  3072.         var fileContent = null;
  3073.         try {
  3074.             var fis = Components.classes['@mozilla.org/network/file-input-stream;1']
  3075.                         .createInstance(Components.interfaces.nsIFileInputStream);
  3076.             fis.init(file, -1, 0, 0);
  3077.             var is = Components.classes['@mozilla.org/intl/converter-input-stream;1']
  3078.                         .createInstance(Components.interfaces.nsIConverterInputStream);
  3079.             is.init(fis, 'UTF-8', 1024, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
  3080.             if(is instanceof Components.interfaces.nsIUnicharLineInputStream) {
  3081.                 var line = {};
  3082.                 var cont;
  3083.                 do {
  3084.                     cont = is.readLine(line);
  3085.                     if(fileContent == null) {
  3086.                         fileContent = line.value;
  3087.                     } else {
  3088.                         fileContent += '\n'+line.value;
  3089.                     }
  3090.                 } while (cont);
  3091.             }
  3092.             is.close();
  3093.             fis.close();
  3094.         } catch(e) {
  3095.             this.log(e);
  3096.         }
  3097.         return fileContent;
  3098.     },
  3099.  
  3100.     setFileContent: function(file, str) {
  3101.         try {
  3102.             var fos = Components.classes['@mozilla.org/network/file-output-stream;1']
  3103.                         .createInstance(Components.interfaces.nsIFileOutputStream);
  3104.             fos.init(file, 0x02 | 0x08 | 0x20, 0664, 0);
  3105.             var os = Components.classes['@mozilla.org/intl/converter-output-stream;1']
  3106.                         .createInstance(Components.interfaces.nsIConverterOutputStream);
  3107.             os.init(fos, 'UTF-8', 4096, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
  3108.             os.writeString(str);
  3109.             os.close();
  3110.             fos.close();
  3111.         } catch(e) {
  3112.             this.log(e);
  3113.         }
  3114.     },
  3115.     
  3116.     getFieldRulesFile: function() {
  3117.         var file = this.getConfigDirectory();
  3118.         file.append('fieldRules.txt');
  3119.         if(!file.exists()) {
  3120.             file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0660);
  3121.         }
  3122.         return file;
  3123.     },
  3124.  
  3125.     getConfigDirectory: function() {
  3126.         var configDirectory; 
  3127.         if(this.autofillFormsPrefs.prefHasUserValue('configDirectory')) {
  3128.             try {
  3129.                 configDirectory = this.autofillFormsPrefs.getComplexValue(
  3130.                                         'configDirectory',
  3131.                                         Components.interfaces.nsILocalFile
  3132.                 );
  3133.             } catch(e) {
  3134.                 this.autofillFormsPrefs.clearUserPref('configDirectory');
  3135.             }            
  3136.         }
  3137.         if(!configDirectory) {
  3138.             configDirectory = this.getDefaultConfigDirectory();
  3139.         }
  3140.         return configDirectory;
  3141.     },
  3142.  
  3143.     getDefaultConfigDirectory: function() {
  3144.         // Use a directory "autofillForms@blueimp.net" inside Firefox profile directory as default:
  3145.         var configDirectory = Components.classes['@mozilla.org/file/directory_service;1']
  3146.                                 .getService(Components.interfaces.nsIProperties)
  3147.                                 .get('ProfD', Components.interfaces.nsILocalFile);
  3148.         configDirectory.append('autofillForms@blueimp.net');
  3149.         if(!configDirectory.exists()) {
  3150.            configDirectory.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0770);
  3151.         }
  3152.         return configDirectory;
  3153.     },
  3154.     
  3155.     setConfigDirectory: function(textBox) {
  3156.         try {        
  3157.             // Create a file picker instance:
  3158.             var fp = Components.classes['@mozilla.org/filepicker;1']
  3159.                         .createInstance(Components.interfaces.nsIFilePicker);
  3160.             
  3161.             // Initialize the file picker window:
  3162.             fp.init(
  3163.                 window,
  3164.                 this.getStringBundle().getString('selectConfigDirectory'),
  3165.                 Components.interfaces.nsIFilePicker.modeGetFolder
  3166.             );
  3167.             
  3168.             // Show the file picker window:
  3169.             var rv = fp.show();
  3170.             
  3171.             if (rv == Components.interfaces.nsIFilePicker.returnOK) {
  3172.                 var newDir = fp.file;
  3173.                 if(newDir.path == this.getConfigDirectory().path) {
  3174.                     return;
  3175.                 }
  3176.                 this.moveConfigFiles(newDir);                
  3177.                 // Save the selected directory in the preferences:
  3178.                 this.autofillFormsPrefs.setComplexValue(
  3179.                     'configDirectory',
  3180.                     Components.interfaces.nsILocalFile, newDir
  3181.                 );
  3182.                 if(textBox) {
  3183.                     // Set the textbox value to the directory path:
  3184.                     textBox.value = newDir.path;
  3185.                 }
  3186.             }
  3187.         } catch(e) {
  3188.             this.log(e);
  3189.         }
  3190.     },
  3191.     
  3192.     resetConfigDirectory: function(textBox) {
  3193.         if(this.autofillFormsPrefs.prefHasUserValue('configDirectory')) {        
  3194.             var newDir = this.getDefaultConfigDirectory();
  3195.             this.moveConfigFiles(newDir);
  3196.             this.autofillFormsPrefs.clearUserPref('configDirectory');
  3197.             if(textBox) {
  3198.                 // Set the textbox value to an empty string:
  3199.                 textBox.value = '';
  3200.             }
  3201.         }
  3202.     },
  3203.     
  3204.     openConfigDirectory: function() {
  3205.         var configDirectory = this.getConfigDirectory();
  3206.         if(configDirectory) {
  3207.             try {
  3208.                 // Open the config directory in the operating system file manager:
  3209.                 configDirectory.reveal();
  3210.             } catch(e) {
  3211.                 // reveal method may not be supported on some platforms,
  3212.                 // use nsIExternalProtocolService instead: 
  3213.                 var uri = Components.classes["@mozilla.org/network/io-service;1"]
  3214.                     .getService(Components.interfaces.nsIIOService)
  3215.                     .newFileURI(configDirectory);        
  3216.                 var protocolSvc = 
  3217.                 Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
  3218.                     .getService(Components.interfaces.nsIExternalProtocolService);
  3219.                 protocolSvc.loadUrl(uri);
  3220.             }
  3221.         }
  3222.     },
  3223.     
  3224.     moveConfigFiles: function(newDir) {
  3225.         if(this.checkConfigDirectoryOverwrite(newDir)) {
  3226.             this.moveFile(this.getFieldRulesFile(), newDir);
  3227.             this.moveFile(this.getDynamicTagsFile(), newDir);
  3228.             this.moveFile(this.getDynamicTagCodesFile(), newDir);
  3229.             this.moveFile(this.getProfileLabelsFile(), newDir);
  3230.             this.moveFile(this.getProfileSiteRulesFile(), newDir);
  3231.             return true;
  3232.         }
  3233.         return false;
  3234.     },
  3235.     
  3236.     importFromConfigDirectory: function() {
  3237.         var ok = true;
  3238.         if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
  3239.             ok = this.getPrompts().confirm(
  3240.                 window,
  3241.                 this.getStringBundle().getString('importFromConfigDirectoryTitle'),
  3242.                 this.getStringBundle().getString('importFromConfigDirectoryText')
  3243.             );
  3244.         }
  3245.         if(ok) {
  3246.             this.importFieldRulesFromConfigDirectory();
  3247.             this.importDynamicTagsFromConfigDirectory();
  3248.             this.importDynamicTagCodesFromConfigDirectory();
  3249.             this.importProfileLabelsFromConfigDirectory();
  3250.             this.importProfileSiteRulesFromConfigDirectory();
  3251.         }
  3252.     },
  3253.     
  3254.     exportToConfigDirectory: function() {
  3255.         if(this.checkConfigDirectoryOverwrite(this.getConfigDirectory())) {
  3256.             this.exportFieldRulesToConfigDirectory();
  3257.             this.exportDynamicTagsToConfigDirectory();
  3258.             this.exportDynamicTagCodesToConfigDirectory();
  3259.             this.exportProfileLabelsToConfigDirectory();
  3260.             this.exportProfileSiteRulesToConfigDirectory();
  3261.             return true;
  3262.         }
  3263.         return false;
  3264.     },
  3265.     
  3266.     moveFile: function(file, newDir, newFileName) {
  3267.         try {
  3268.             // The new fileName - uses the current fileName if empty:
  3269.             newFileName = (typeof newFileName == 'string') ? newFileName : null;
  3270.             
  3271.             file.moveTo(newDir, newFileName);
  3272.             return true;
  3273.         } catch(e) {
  3274.             this.log(e);
  3275.             return false;
  3276.         }
  3277.     },
  3278.     
  3279.     checkConfigDirectoryOverwrite: function(newDir) {
  3280.         var ok = true;
  3281.         if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
  3282.             if(newDir.directoryEntries.hasMoreElements()) {
  3283.                 ok = this.getPrompts().confirm(
  3284.                     window,
  3285.                     this.getStringBundle().getString('newConfigDirectoryNotEmptyTitle'),
  3286.                     this.getStringBundle().getString('newConfigDirectoryNotEmptyText')
  3287.                 );
  3288.             }
  3289.         }
  3290.         return ok;
  3291.     },
  3292.  
  3293.     exportFieldRulesToConfigDirectory: function() {
  3294.         var prefString;
  3295.         // Get the fieldRules string from the preferences:
  3296.         prefString = this.autofillFormsPrefs
  3297.                             .getComplexValue('fieldRules',Components.interfaces.nsIPrefLocalizedString)
  3298.                             .data;
  3299.         if(prefString) {
  3300.             this.setFileContent(this.getFieldRulesFile(), prefString);
  3301.         }
  3302.     },
  3303.     
  3304.     importFieldRulesFromConfigDirectory: function() {
  3305.         var prefString;
  3306.         prefString = this.getFileContent(this.getFieldRulesFile());
  3307.         if(prefString) {
  3308.             // Store the fieldRules as unicode string in the preferences:
  3309.             this.autofillFormsPrefs.setComplexValue(
  3310.                 'fieldRules',
  3311.                 Components.interfaces.nsISupportsString,
  3312.                 this.getUnicodeString(prefString)
  3313.             );
  3314.         }
  3315.     },
  3316.     
  3317.     getFieldRules: function(profileIndex) {
  3318.         if(this.fieldRules == null) {
  3319.             this.fieldRules = new Array();
  3320.  
  3321.             var prefString;        
  3322.             if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  3323.                 // Get the fieldRules string from the fieldRules file in the configDirectory:
  3324.                 prefString = this.getFileContent(this.getFieldRulesFile());
  3325.             }        
  3326.             if(!prefString) {
  3327.                 // Get the fieldRules string from the preferences:
  3328.                 prefString = this.autofillFormsPrefs
  3329.                                     .getComplexValue('fieldRules',Components.interfaces.nsIPrefLocalizedString)
  3330.                                     .data;
  3331.             }
  3332.             
  3333.             // On change of the "storeEncrypted" setting, we must decrypt or may not decrypt
  3334.             // the prefString in opposition to the setting - the "invertedSetting" helper var
  3335.             // helps to identify this situation:
  3336.             var boolTest = this.invertedSetting ? false : true;
  3337.             
  3338.             // If the fieldRules are stored encrypted, decrypt the prefString:
  3339.             if(this.autofillFormsPrefs.getBoolPref('storeEncrypted') == boolTest) {
  3340.                 try {
  3341.                     // nsISecretDecoderRing fails to handle characters above ISO-8859-1 charset
  3342.                     // The usage of encodeURI/decodeURI on the fieldRule properties bypasses this problem
  3343.                     prefString = this.getCryptoService().decryptString(prefString);
  3344.                 } catch(e) {
  3345.                     // Decrypting failed - return an empty default profile:
  3346.                     this.fieldRules.push(new Array());
  3347.                     this.profileIndex = 0;
  3348.                     return this.fieldRules[0];
  3349.                 }
  3350.             }
  3351.             
  3352.             // Get the profiles (separated by \n\n):
  3353.             var profiles = prefString.split('\n\n');
  3354.         
  3355.             for(var i=0; i<profiles.length; i++) {
  3356.                 // Create an array for each profile:
  3357.                 this.fieldRules.push(new Array());
  3358.                 
  3359.                 // Get the fieldRules rows (separated by \n):
  3360.                 var rows = profiles[i].split('\n');
  3361.                 if(rows[0]) {
  3362.                     for(var j=0; j<rows.length; j++) {
  3363.                         if(!rows[j])
  3364.                             continue;
  3365.                             
  3366.                         // Get the fieldRules column items (separated by \t):
  3367.                         var cols = rows[j].split('\t');
  3368.                                                 
  3369.                         // Create fieldRules objects and save them in the current fieldRules Array:
  3370.                         if(cols.length && cols.length == 6) {
  3371.  
  3372.                             // Decode the fieldRule properties:
  3373.                             for(var k=0; k<cols.length; k++) {
  3374.                                 cols[k] = decodeURI(cols[k]);
  3375.                             }
  3376.  
  3377.                             this.fieldRules[i].push(
  3378.                                 this.createFieldRule(
  3379.                                     cols[0],cols[1],cols[2],cols[3],
  3380.                                     (cols[4] != 'false'),
  3381.                                     (cols[5] != 'false')
  3382.                                 )
  3383.                             );
  3384.                         }
  3385.                     }
  3386.                 } else
  3387.                     this.fieldRules[i] = new Array();
  3388.             }
  3389.         }
  3390.  
  3391.         profileIndex = (typeof profileIndex != 'undefined') ? profileIndex : this.getProfileIndex();
  3392.         
  3393.         // Return the fieldRules for the selected profile if profileIndex is not out of range:
  3394.         if(profileIndex >= 0 && profileIndex < this.fieldRules.length)
  3395.             return this.fieldRules[profileIndex];
  3396.         else {
  3397.             this.profileIndex = 0;
  3398.             if(this.fieldRules[0] == null)
  3399.                 this.fieldRules[0] = new Array();
  3400.             return this.fieldRules[0];
  3401.         }
  3402.     },
  3403.     
  3404.     setFieldRules: function() {
  3405.         if(this.fieldRules == null) {
  3406.             // Initialize the field rules:
  3407.             this.getFieldRules();
  3408.         }
  3409.             
  3410.         var profiles = '';
  3411.         var rows, cols;
  3412.         for(var i=0; i < this.fieldRules.length; i++) {
  3413.             rows = '';
  3414.             for(var j=0; j<this.fieldRules[i].length; j++) {
  3415.                 cols = null;
  3416.                 for(var property in this.fieldRules[i][j]) {
  3417.                     if(cols == null)
  3418.                         cols = '';
  3419.                     else
  3420.                         cols += '\t';
  3421.                     // Encode the fieldRule property before adding it to the string:
  3422.                     cols += encodeURI(this.fieldRules[i][j][property]);
  3423.                 }
  3424.                 if(j!=0)
  3425.                     rows += '\n';
  3426.                 rows += cols;
  3427.             }
  3428.             if(i!=0)
  3429.                 profiles += '\n\n';
  3430.             profiles += rows;
  3431.         }
  3432.  
  3433.         // If the fieldRules are to be stored encrypted, encrypt the prefString:
  3434.         if(this.autofillFormsPrefs.getBoolPref('storeEncrypted')) {
  3435.             try {
  3436.                 // nsISecretDecoderRing fails to handle characters above ISO-8859-1 charset
  3437.                 // The usage of encodeURI/decodeURI on the fieldRule properties bypasses this problem
  3438.                 profiles = this.getCryptoService().encryptString(profiles);
  3439.             } catch(e) {
  3440.                 // Decrypting failed - return:
  3441.                 return;
  3442.             }
  3443.         }
  3444.  
  3445.         if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  3446.             this.setFileContent(this.getFieldRulesFile(), profiles);
  3447.         } else {
  3448.             // Store the fieldRules objects as unicode string in the preferences:
  3449.             this.autofillFormsPrefs.setComplexValue(
  3450.                 'fieldRules',
  3451.                 Components.interfaces.nsISupportsString,
  3452.                 this.getUnicodeString(profiles)
  3453.             );
  3454.         }
  3455.     },
  3456.     
  3457.     copyFieldRules: function(origin) {
  3458.         var copy = new Array();
  3459.         for(var i=0; i<origin.length; i++) {
  3460.             copy.push(
  3461.                 this.createFieldRule(
  3462.                     origin[i]['fieldRuleName'],
  3463.                     origin[i]['fieldRuleValue'],
  3464.                     origin[i]['fieldRuleFieldRule'],
  3465.                     origin[i]['fieldRuleSiteRule'],
  3466.                     origin[i]['fieldRuleOverwrite'],
  3467.                     origin[i]['fieldRuleEnabled']
  3468.                 )
  3469.             )
  3470.         }
  3471.         return copy;
  3472.     },
  3473.     
  3474.     importProfile: function() {
  3475.         try {
  3476.             var file = this.filePicker('modeOpen', this.getStringBundle().getString('importProfile'));
  3477.             if(file) {
  3478.                 var fis = Components.classes['@mozilla.org/network/file-input-stream;1']
  3479.                                 .createInstance(Components.interfaces.nsIFileInputStream);
  3480.                 fis.init(file, -1, 0, 0);
  3481.  
  3482.                 var is = Components.classes['@mozilla.org/intl/converter-input-stream;1']
  3483.                             .createInstance(Components.interfaces.nsIConverterInputStream);
  3484.                 is.init(fis, 'UTF-8', 1024, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
  3485.  
  3486.                 if(this.fieldRules == null) {
  3487.                     // Initialize the field rules:
  3488.                     this.getFieldRules();
  3489.                 }
  3490.                 
  3491.                 // Create a new fieldRule profile:
  3492.                 var newProfileLabel = '';
  3493.                 var newProfileSiteRule = '(?:)';
  3494.                 var newProfileIndex = this.fieldRules.length;
  3495.                 this.fieldRules.push(new Array());
  3496.  
  3497.                 if (is instanceof Components.interfaces.nsIUnicharLineInputStream) {                    
  3498.                     var line = {};
  3499.                     var cont;
  3500.                     do {
  3501.                         cont = is.readLine(line);
  3502.  
  3503.                         // Get the fieldRules column items (separated by \t) from the file:
  3504.                         var cols = line.value.split('\t');
  3505.                         
  3506.                         if(cols.length && cols.length < 6 && cols[0] == 'autofillForms@blueimp.net') {
  3507.                             // The first row has the following syntax (added SiteRule for version 0.9.1):
  3508.                             // autofillForms@blueimp.net    Version    Label    SiteRule
  3509.                             if(cols.length >= 3) {
  3510.                                 newProfileLabel = cols[2];    
  3511.                             }
  3512.                             if(cols.length >= 4) {
  3513.                                 try {
  3514.                                     newProfileSiteRule = this.getRegExpStr(cols[3]);
  3515.                                 } catch(e) {
  3516.                                     // Catch missing or invalid site rule
  3517.                                 }
  3518.                             }
  3519.                         } else if(cols.length && cols.length == 6) {
  3520.                             // Create fieldRules objects and save them in the fieldRules Array:
  3521.                             this.fieldRules[newProfileIndex].push(
  3522.                                 this.createFieldRule(
  3523.                                     cols[0],cols[1],cols[2],cols[3],
  3524.                                     (cols[4] != 'false'),
  3525.                                     (cols[5] != 'false')
  3526.                                 )
  3527.                             );
  3528.                         }                            
  3529.  
  3530.                     } while (cont);
  3531.                 }
  3532.  
  3533.                 // Add profile label for default empty profile:
  3534.                 if(this.getProfileLabels().length == 0) {
  3535.                     this.getProfileLabels().push(this.getUniqueProfileLabel());
  3536.                 }
  3537.                 
  3538.                 // Add the newProfileLabel to the profileLabels list (make sure it is unique):
  3539.                 this.getProfileLabels().push(this.getUniqueProfileLabel(newProfileLabel));
  3540.                 // Save the profileLabels list in the preferences:
  3541.                 this.setProfileLabels(this.getProfileLabels());
  3542.                 // Add a new profileSiteRule:
  3543.                 this.getProfileSiteRules().push(newProfileSiteRule);
  3544.                 // Save the profileSiteRules in the preferences:
  3545.                 this.setProfileSiteRules(this.getProfileSiteRules());                
  3546.                 // Update the profiles lists:
  3547.                 this.initProfilesLists();
  3548.  
  3549.                 // Update the fieldRules:
  3550.                 this.setFieldRules();
  3551.  
  3552.                 is.close();
  3553.                 
  3554.                 fis.close();
  3555.             }
  3556.         } catch(e) {
  3557.             this.log(e);
  3558.         }
  3559.     },
  3560.     
  3561.     exportProfile: function() {
  3562.         try {
  3563.             var file = this.filePicker(
  3564.                 'modeSave',
  3565.                 this.getStringBundle().getString('exportProfile'),
  3566.                 this.getProfileLabel(this.getProfileIndex())+'.txt'
  3567.             );
  3568.             if(file) {
  3569.                 var fos = Components.classes['@mozilla.org/network/file-output-stream;1'].
  3570.                                         createInstance(Components.interfaces.nsIFileOutputStream);
  3571.                 fos.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
  3572.                 
  3573.                 var os = Components.classes['@mozilla.org/intl/converter-output-stream;1']
  3574.                             .createInstance(Components.interfaces.nsIConverterOutputStream);
  3575.                 os.init(fos, 'UTF-8', 4096, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
  3576.                 
  3577.                 var em = Components.classes['@mozilla.org/extensions/manager;1']
  3578.                             .getService(Components.interfaces.nsIExtensionManager);
  3579.                             
  3580.                 var header = 'autofillForms@blueimp.net' + '\t'
  3581.                                 + em.getItemForID('autofillForms@blueimp.net').version + '\t'
  3582.                                 + this.getProfileLabel(this.getProfileIndex()) + '\t'
  3583.                                 + this.getProfileSiteRule(this.getProfileIndex()) + '\n';
  3584.                 os.writeString(header);
  3585.                 
  3586.                 // Write the rules to file analog to storing them in the preferences:
  3587.                 var cols;
  3588.                 for(var i=0; i<this.getFieldRules().length; i++) {
  3589.                     cols = null;
  3590.                     for(var property in this.getFieldRules()[i]) {
  3591.                         if(cols == null)
  3592.                             cols = '';
  3593.                         else
  3594.                             cols += '\t';
  3595.                         cols += this.getFieldRules()[i][property];
  3596.                     }
  3597.                     os.writeString('\n' + cols);
  3598.                 }
  3599.  
  3600.                 os.close();
  3601.                 
  3602.                 fos.close();                
  3603.             }
  3604.         } catch(e) {
  3605.             this.log(e);
  3606.         }
  3607.     },
  3608.     
  3609.     filePicker: function(mode, title, fileName) {
  3610.         try {        
  3611.             // Create a file picker instance:
  3612.             var fp = Components.classes['@mozilla.org/filepicker;1']
  3613.                         .createInstance(Components.interfaces.nsIFilePicker);
  3614.  
  3615.             // The filename suggested to the user as a default:
  3616.             if(fileName) {
  3617.                 fp.defaultString = fileName;
  3618.             }
  3619.                         
  3620.             // Initialize the file picker window:
  3621.             fp.init(
  3622.                 window,
  3623.                 title,
  3624.                 Components.interfaces.nsIFilePicker[mode]
  3625.             );
  3626.             
  3627.             // Show the file picker window:
  3628.             var rv = fp.show();
  3629.             
  3630.             if(rv==Components.interfaces.nsIFilePicker.returnOK || rv==Components.interfaces.nsIFilePicker.returnReplace)
  3631.                 return fp.file;
  3632.             else
  3633.                 return null;
  3634.         } catch(e) {
  3635.             return null;
  3636.         }
  3637.     },
  3638.  
  3639.     getUnicodeString: function(stringData) {
  3640.         // Create an Unicode String:
  3641.         var str = Components.classes['@mozilla.org/supports-string;1']
  3642.                     .createInstance(Components.interfaces.nsISupportsString);
  3643.         // Set the String value:
  3644.         str.data = stringData;
  3645.         // Return the Unicode String:
  3646.         return str;
  3647.     },
  3648.  
  3649.     getStringBundle: function() {
  3650.         return document.getElementById('autofillFormsStringBundle');
  3651.     },
  3652.  
  3653.     getDoc: function(win) {
  3654.         if(win)
  3655.             return win.document;
  3656.         else if(content)
  3657.             return content.document;
  3658.         else
  3659.             return this.getBrowser().contentDocument;
  3660.     },
  3661.  
  3662.     getWin: function() {
  3663.         if(content)
  3664.             return content;
  3665.         else
  3666.             return this.getBrowser().contentWindow;
  3667.     },
  3668.     
  3669.     getBrowser: function() {
  3670.         try {
  3671.             return gBrowser;
  3672.         } catch(e) {
  3673.             // gBrowser is not available, so make use of the WindowMediator service instead:
  3674.             return this.getWindowMediator().getMostRecentWindow('navigator:browser').getBrowser();
  3675.         }
  3676.     },
  3677.  
  3678.     getWindowMediator: function() {
  3679.         return Components.classes['@mozilla.org/appshell/window-mediator;1']
  3680.                 .getService(Components.interfaces.nsIWindowMediator);
  3681.     },
  3682.  
  3683.     getRegExpStr: function(str) {
  3684.         // Create a RegExp object using the given String:
  3685.         var regExpStr = new RegExp(str).toString();
  3686.         // Return the String representation without the surrounding slashes:
  3687.         return regExpStr.substr(1,regExpStr.length-2);
  3688.     },
  3689.  
  3690.     getPrefManager: function() {
  3691.         return Components.classes['@mozilla.org/preferences-service;1']
  3692.                 .getService(Components.interfaces.nsIPrefService);
  3693.     },
  3694.     
  3695.     getCryptoService: function() {
  3696.         return Components.classes['@mozilla.org/security/sdr;1']
  3697.                 .createInstance(Components.interfaces.nsISecretDecoderRing);
  3698.     },
  3699.     
  3700.     getPrompts: function() {
  3701.         return Components.classes['@mozilla.org/embedcomp/prompt-service;1']
  3702.                 .getService(Components.interfaces.nsIPromptService);
  3703.     },
  3704.  
  3705.     recognizeMouseButton: function(event) {
  3706.         var modifiers = new Array();
  3707.         
  3708.         // Get the modifiers:
  3709.         if(event.altKey) modifiers.push('alt');
  3710.         if(event.ctrlKey) modifiers.push('control');
  3711.         if(event.metaKey) modifiers.push('meta');
  3712.         if(event.shiftKey) modifiers.push('shift');
  3713.         
  3714.         // Return a mouseButtonObj:
  3715.         return this.mouseButtonFactory(modifiers, 'mousebutton'+event.button);
  3716.     },
  3717.     
  3718.     mouseButtonFactory: function(modifiers, mouseButton) {
  3719.         if(typeof arguments.callee.mouseButtonObj == 'undefined') {
  3720.             arguments.callee.mouseButtonObj = function(modifiers, mouseButton) {
  3721.                 this.modifiers = modifiers ? modifiers : new Array();
  3722.                 this.mouseButton = mouseButton;
  3723.                 this.toString = function() {
  3724.                     if(this.modifiers.length) {
  3725.                         return this.modifiers.join('+')+'+'+this.mouseButton;
  3726.                     } else {
  3727.                         return this.mouseButton;
  3728.                     }
  3729.                 }
  3730.                 this.equals = function(mouseButtonObj) {
  3731.                     if(this.mouseButton != mouseButtonObj.mouseButton) {
  3732.                         return false;
  3733.                     }
  3734.                     if(this.modifiers.length != mouseButtonObj.modifiers.length) {
  3735.                         return false;
  3736.                     }
  3737.                     for(var i=0; i<this.modifiers.length; i++) {
  3738.                         if(this.modifiers[i] != mouseButtonObj.modifiers[i]) {
  3739.                             return false;
  3740.                         }
  3741.                     }
  3742.                     return true;
  3743.                 }
  3744.                 return this;
  3745.             }
  3746.         }
  3747.         return new arguments.callee.mouseButtonObj(modifiers, mouseButton);
  3748.     },
  3749.  
  3750.     getFormattedMouseButton: function(mouseButtonObj) {
  3751.         var formattedMouseButton = '';
  3752.         if(!mouseButtonObj.mouseButton) {
  3753.             return formattedMouseButton;
  3754.         }
  3755.         // Add the modifiers:
  3756.         for(var i=0; i < mouseButtonObj.modifiers.length; i++) {
  3757.             try {
  3758.                 formattedMouseButton += this.getStringBundle().getString(mouseButtonObj.modifiers[i])+'+';
  3759.             } catch(e) {
  3760.                 this.log(e);
  3761.                 // Error in shortcut string, return empty String:
  3762.                 return '';
  3763.             }
  3764.         }
  3765.         try {
  3766.             formattedMouseButton += this.getStringBundle().getString(mouseButtonObj.mouseButton);
  3767.         } catch(e) {
  3768.             // No localization for this mouse button, add generic button string :
  3769.             formattedMouseButton += this.getStringBundle().getString('mousebutton');
  3770.             // Add the index of the given mouseButton:
  3771.             formattedMouseButton += ' '+mouseButtonObj.mouseButton.substr('mousebutton'.length);
  3772.         }
  3773.         return formattedMouseButton;
  3774.     },
  3775.     
  3776.     applyMouseButton: function(event, id) {
  3777.         // Recognize the mouse button event:
  3778.         var mouseButtonObj = this.recognizeMouseButton(event);
  3779.         if(!mouseButtonObj)
  3780.             return;
  3781.         // Ignore the right mouse button (mousebutton2), as this already invokes the context menu:
  3782.         if(mouseButtonObj.mouseButton == 'mousebutton2') {
  3783.             return;
  3784.         }
  3785.         // Save the new mouse button object:
  3786.         this.setMouseButton(id, mouseButtonObj);
  3787.         // Update the mouse button textbox:
  3788.         if(event.view.document && event.view.document.getElementById(id)) {
  3789.             event.view.document.getElementById(id).value = this.getFormattedMouseButton(mouseButtonObj);
  3790.         }
  3791.     },
  3792.     
  3793.     disableMouseButton: function(event, id) {
  3794.         // Disable the mouse button:
  3795.         this.setMouseButton(id, null);
  3796.         // Update the mouse button textbox:
  3797.         if(event.view.document && event.view.document.getElementById(id)) {
  3798.             event.view.document.getElementById(id).value = '';
  3799.         }
  3800.     },
  3801.  
  3802.     getMouseButton: function(id) {
  3803.         if(this.mouseButton == null) {
  3804.             // Create a new mouseButton container object:
  3805.             this.mouseButton = new Object();
  3806.         }
  3807.         if(this.mouseButton[id] == null) {
  3808.             var mouseButtonItems = this.autofillFormsPrefs.getCharPref(id).split('+');
  3809.             var mouseButton;
  3810.             if(mouseButtonItems.length == 0) {
  3811.                 mouseButton = '';
  3812.             } else {
  3813.                 // Remove the last element and save it as mouseButton
  3814.                 // the remaining mouseButtonItems are the modifiers:
  3815.                 mouseButton = mouseButtonItems.pop();
  3816.             }            
  3817.             // Create a new mouseButton object:
  3818.             this.mouseButton[id] = this.mouseButtonFactory(mouseButtonItems, mouseButton);
  3819.         }
  3820.         return this.mouseButton[id];
  3821.     },
  3822.     
  3823.     setMouseButton: function(id, mouseButtonObj) {
  3824.         var stringData;
  3825.         if(mouseButtonObj) {
  3826.             stringData = mouseButtonObj.toString();
  3827.         } else {
  3828.             stringData = '';
  3829.         }
  3830.         // Save the mouseButtonObj as Unicode String in the preferences:
  3831.         this.autofillFormsPrefs.setComplexValue(
  3832.             id,
  3833.             Components.interfaces.nsISupportsString,
  3834.             this.getUnicodeString(stringData)
  3835.         );
  3836.     },
  3837.     
  3838.     recognizeKeys: function(event) {
  3839.         var modifiers = new Array();
  3840.         var key = '';
  3841.         var keycode = '';
  3842.         
  3843.         // Get the modifiers:
  3844.         if(event.altKey) modifiers.push('alt');
  3845.         if(event.ctrlKey) modifiers.push('control');
  3846.         if(event.metaKey) modifiers.push('meta');
  3847.         if(event.shiftKey) modifiers.push('shift');
  3848.         
  3849.         // Get the key or keycode:
  3850.         if(event.charCode) {
  3851.             key = String.fromCharCode(event.charCode).toUpperCase();
  3852.         } else {
  3853.             // Get the keycode from the keycodes list:
  3854.             keycode = this.getKeyCodes()[event.keyCode];
  3855.             if(!keycode) {
  3856.                 return null;
  3857.             }
  3858.         }
  3859.         
  3860.         // Shortcut may be anything, but not 'VK_TAB' alone (without modifiers),
  3861.         // as this button is used to change focus to the 'Apply' button: 
  3862.         if(modifiers.length > 0 || keycode != 'VK_TAB') {
  3863.             return this.shortcutFactory(modifiers, key, keycode);
  3864.         }
  3865.         return null;
  3866.     },
  3867.  
  3868.     shortcutFactory: function(modifiers, key, keycode) {
  3869.         if(typeof arguments.callee.shortcut == 'undefined') {
  3870.             arguments.callee.shortcut = function(modifiers, key, keycode) {
  3871.                 this.modifiers = modifiers ? modifiers : new Array();
  3872.                 this.key = key;
  3873.                 this.keycode = keycode;
  3874.                 this.toString = function() {
  3875.                     if(this.modifiers.length) {
  3876.                         return this.modifiers.join('+')+'+'+this.key+this.keycode;
  3877.                     } else {
  3878.                         return this.key+this.keycode;
  3879.                     }
  3880.                 }
  3881.                 this.equals = function(shortcut) {
  3882.                     if(this.key != shortcut.key) {
  3883.                         return false;
  3884.                     }
  3885.                     if(this.keycode != shortcut.keycode) {
  3886.                         return false;
  3887.                     }
  3888.                     if(this.modifiers.length != shortcut.modifiers.length) {
  3889.                         return false;
  3890.                     }
  3891.                     for(var i=0; i<this.modifiers.length; i++) {
  3892.                         if(this.modifiers[i] != shortcut.modifiers[i]) {
  3893.                             return false;
  3894.                         }
  3895.                     }
  3896.                     return true;
  3897.                 }
  3898.                 return this;
  3899.             }
  3900.         }
  3901.         return new arguments.callee.shortcut(modifiers, key, keycode);
  3902.     },
  3903.     
  3904.     getKeyCodes: function() {
  3905.         var keycodes = new Array();
  3906.         // Get the list of keycodes from the KeyEvent object:
  3907.         for(var property in KeyEvent) {
  3908.             keycodes[KeyEvent[property]] = property.replace('DOM_','');
  3909.         }
  3910.         // VK_BACK_SPACE (index 8) must be VK_BACK:
  3911.         keycodes[8] = 'VK_BACK';
  3912.         return keycodes;
  3913.     },
  3914.  
  3915.     applyShortcut: function(event, id) {
  3916.         // Recognize the pressed keys:
  3917.         var shortcut = this.recognizeKeys(event)
  3918.         if(!shortcut)
  3919.             return;
  3920.         // Save the new shortcut:
  3921.         this.setShortcut(id, shortcut);
  3922.         // Update the shortcut textbox:
  3923.         if(event.view.document && event.view.document.getElementById(id)) {
  3924.             event.view.document.getElementById(id).value = this.getFormattedShortcut(shortcut);
  3925.         }
  3926.     },
  3927.     
  3928.     disableShortcut: function(event, id) {
  3929.         // Disable the shortcut:
  3930.         this.setShortcut(id, null);
  3931.         // Update the shortcut textbox:
  3932.         if(event.view.document && event.view.document.getElementById(id)) {
  3933.             event.view.document.getElementById(id).value = '';
  3934.         }
  3935.     },
  3936.     
  3937.     getShortcut: function(id) {
  3938.         if(this.shortcut == null) {
  3939.             // Create a new shortcut container object:
  3940.             this.shortcut = new Object();
  3941.         }
  3942.         if(this.shortcut[id] == null) {
  3943.             var key = null;
  3944.             var keycode = null;
  3945.             var shortcutItems = this.autofillFormsPrefs
  3946.                                 .getComplexValue(id,Components.interfaces.nsIPrefLocalizedString)
  3947.                                 .data.split('+');
  3948.             if(shortcutItems.length > 0) {
  3949.                 // Remove the last element and save it as key
  3950.                 // the remaining shortcutItems are the modifiers:
  3951.                 key = shortcutItems.pop();
  3952.                 // Check if the key is a keycode:
  3953.                 if(key.indexOf('VK') == 0) {
  3954.                     keycode    = key;
  3955.                     key = null;
  3956.                 }
  3957.             }            
  3958.             // Create a new shortcut object:
  3959.             this.shortcut[id] = this.shortcutFactory(shortcutItems, key, keycode);
  3960.         }
  3961.         return this.shortcut[id];
  3962.     },
  3963.     
  3964.     setShortcut: function(id, shortcut) {
  3965.         var stringData;
  3966.         if(shortcut) {
  3967.             stringData = shortcut.toString();
  3968.         } else {
  3969.             stringData = '';
  3970.         }
  3971.         // Save the shortcut as Unicode String in the preferences:
  3972.         this.autofillFormsPrefs.setComplexValue(
  3973.             id,
  3974.             Components.interfaces.nsISupportsString,
  3975.             this.getUnicodeString(stringData)
  3976.         );
  3977.     },
  3978.     
  3979.     updateShortcut: function(id) {
  3980.         if(this.shortcut == null) {
  3981.             this.shortcut = new Object();
  3982.         }
  3983.         // Setting the shortcut object to "null" will update it on the next getShortcut() call:
  3984.         this.shortcut[id] = null;
  3985.         
  3986.         // Get the keyboard shortcut elements:
  3987.         var modifiers = this.getShortcut(id).modifiers.join(' ');
  3988.         var key = this.getShortcut(id).key;
  3989.         var keycode = this.getShortcut(id).keycode;
  3990.         
  3991.         var domId = 'autofillForms' + id.replace(/shortcut/, 'Shortcut');
  3992.         var command = 'autofillForms' + id.replace(/shortcut/i, '');
  3993.  
  3994.         // Remove current key if existing:
  3995.         if(document.getElementById(domId)) {
  3996.             document.getElementById('mainKeyset').removeChild(
  3997.                 document.getElementById(domId)
  3998.             );
  3999.         }
  4000.         
  4001.         // Check if keyboard shortcut is enabled:
  4002.         if(key || keycode) {
  4003.             // Create a key element:
  4004.             var keyNode = document.createElement('key');
  4005.             
  4006.             keyNode.setAttribute('id', domId);
  4007.             keyNode.setAttribute('command', command);
  4008.             
  4009.             // Set the key attributes from saved shortcut:
  4010.             keyNode.setAttribute('modifiers', modifiers);
  4011.             if(key) {
  4012.                 keyNode.setAttribute('key', key);
  4013.             } else {
  4014.                 keyNode.setAttribute('keycode', keycode);
  4015.             }
  4016.             
  4017.             // Add the key to the mainKeyset:
  4018.             document.getElementById('mainKeyset').appendChild(keyNode);
  4019.         }
  4020.     },
  4021.     
  4022.     getFormattedShortcut: function(shortcut) {
  4023.         var formattedShortcut = '';
  4024.         // Add the modifiers:
  4025.         for(var i=0; i < shortcut.modifiers.length; i++) {
  4026.             try {
  4027.                 formattedShortcut += this.getStringBundle().getString(shortcut.modifiers[i]) + '+';
  4028.             } catch(e) {
  4029.                 // Error in shortcut string, return empty String;
  4030.                 return '';
  4031.             }
  4032.         }
  4033.         if(shortcut.key) {
  4034.             // Add the key:
  4035.             if(shortcut.key == ' ') {
  4036.                 formattedShortcut += this.getStringBundle().getString('VK_SPACE');
  4037.             } else {
  4038.                 formattedShortcut += shortcut.key;
  4039.             }
  4040.         } else if(shortcut.keycode) {
  4041.             // Add the keycode (instead of the key):
  4042.             try {
  4043.                 formattedShortcut += this.getStringBundle().getString(shortcut.keycode);
  4044.             } catch(e) {
  4045.                 formattedShortcut += shortcut.keycode.replace('VK_', '');
  4046.             }
  4047.         }
  4048.         return formattedShortcut;
  4049.     },
  4050.  
  4051.     replaceDynamicTags: function(fieldRuleValue) {
  4052.         // Replace all dynamic tags with the return values of their associated tag codes:
  4053.         for(var j=0; j<this.getDynamicTags().length; j++) {
  4054.             // Catch if the number of tags doesn't match the number of tag codes or if the tag code is invalid:
  4055.             try {
  4056.                 var regExpObj = new RegExp(this.getDynamicTags()[j],'g');
  4057.                 // We use eval() here without restrictions - the given tagCode must be trusted:
  4058.                 fieldRuleValue = fieldRuleValue.replace(regExpObj, eval(this.getDynamicTagCodes()[j]));
  4059.             } catch(e) {
  4060.                 this.log(e);
  4061.             }
  4062.         }
  4063.         return fieldRuleValue;
  4064.     },
  4065.  
  4066.     getDynamicTagsFile: function() {
  4067.         var file = this.getConfigDirectory();
  4068.         file.append('dynamicTags.txt');
  4069.         if(!file.exists()) {
  4070.             file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0660);
  4071.         }
  4072.         return file;
  4073.     },
  4074.  
  4075.     exportDynamicTagsToConfigDirectory: function() {
  4076.         var prefString;
  4077.         // Get the dynamicTags string from the preferences:
  4078.         prefString = this.autofillFormsPrefs
  4079.                             .getComplexValue('dynamicTags',Components.interfaces.nsIPrefLocalizedString)
  4080.                             .data;
  4081.         if(prefString) {
  4082.             this.setFileContent(this.getDynamicTagsFile(), prefString);
  4083.         }
  4084.     },
  4085.     
  4086.     importDynamicTagsFromConfigDirectory: function() {
  4087.         var prefString;
  4088.         prefString = this.getFileContent(this.getDynamicTagsFile());
  4089.         if(prefString) {
  4090.             // Store the dynamicTags as unicode string in the preferences:
  4091.             this.autofillFormsPrefs.setComplexValue(
  4092.                 'dynamicTags',
  4093.                 Components.interfaces.nsISupportsString,
  4094.                 this.getUnicodeString(prefString)
  4095.             );
  4096.         }
  4097.     },
  4098.     
  4099.     getDynamicTags: function() {
  4100.         if(this.dynamicTags == null) {
  4101.             var prefString;
  4102.             if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  4103.                 // Get the dynamicTags string from the dynamicTags file in the configDirectory:
  4104.                 prefString = this.getFileContent(this.getDynamicTagsFile());
  4105.             }
  4106.             if(!prefString) {
  4107.                 prefString = this.autofillFormsPrefs
  4108.                                     .getComplexValue('dynamicTags',Components.interfaces.nsIPrefLocalizedString)
  4109.                                     .data;
  4110.             }        
  4111.             this.dynamicTags = prefString.split('\t');
  4112.         }
  4113.         return this.dynamicTags;
  4114.     },
  4115.  
  4116.     setDynamicTags: function(dynamicTags) {
  4117.         // Save the dynamic tags separated by tabs:
  4118.         var prefString = dynamicTags.join('\t');
  4119.         if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  4120.             this.setFileContent(this.getDynamicTagsFile(), prefString);
  4121.         } else {
  4122.             this.autofillFormsPrefs.setComplexValue(
  4123.                 'dynamicTags',
  4124.                 Components.interfaces.nsISupportsString,
  4125.                 this.getUnicodeString(prefString)
  4126.             );
  4127.         }
  4128.     },
  4129.  
  4130.     getDynamicTagCodesFile: function() {
  4131.         var file = this.getConfigDirectory();
  4132.         file.append('dynamicTagCodes.txt');
  4133.         if(!file.exists()) {
  4134.             file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0660);
  4135.         }
  4136.         return file;
  4137.     },
  4138.  
  4139.     exportDynamicTagCodesToConfigDirectory: function() {
  4140.         var prefString;
  4141.         // Get the dynamicTagCodes string from the preferences:
  4142.         prefString = this.autofillFormsPrefs
  4143.                             .getComplexValue('dynamicTagCodes',Components.interfaces.nsIPrefLocalizedString)
  4144.                             .data;
  4145.         if(prefString) {
  4146.             this.setFileContent(this.getDynamicTagCodesFile(), prefString);
  4147.         }
  4148.     },
  4149.     
  4150.     importDynamicTagCodesFromConfigDirectory: function() {
  4151.         var prefString;
  4152.         prefString = this.getFileContent(this.getDynamicTagCodesFile());
  4153.         if(prefString) {
  4154.             // Store the dynamicTagCodes as unicode string in the preferences:
  4155.             this.autofillFormsPrefs.setComplexValue(
  4156.                 'dynamicTagCodes',
  4157.                 Components.interfaces.nsISupportsString,
  4158.                 this.getUnicodeString(prefString)
  4159.             );
  4160.         }
  4161.     },
  4162.     
  4163.     getDynamicTagCodes: function() {
  4164.         if(this.dynamicTagCodes == null) {
  4165.             var prefString;
  4166.             if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  4167.                 // Get the dynamicTagCodes string from the dynamicTagCodes file in the configDirectory:
  4168.                 prefString = this.getFileContent(this.getDynamicTagCodesFile());
  4169.             }
  4170.             if(!prefString) {
  4171.                 prefString = this.autofillFormsPrefs
  4172.                                     .getComplexValue('dynamicTagCodes',Components.interfaces.nsIPrefLocalizedString)
  4173.                                     .data;
  4174.             }
  4175.             this.dynamicTagCodes = prefString.split('\t');
  4176.         }        
  4177.         return this.dynamicTagCodes;
  4178.     },
  4179.  
  4180.     setDynamicTagCodes: function(dynamicTagCodes) {
  4181.         // Save the dynamic tag codes separated by tabs:
  4182.         var prefString = dynamicTagCodes.join('\t');
  4183.         if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  4184.             this.setFileContent(this.getDynamicTagCodesFile(), prefString);
  4185.         } else {
  4186.             this.autofillFormsPrefs.setComplexValue(
  4187.             'dynamicTagCodes',
  4188.                 Components.interfaces.nsISupportsString,
  4189.                 this.getUnicodeString(prefString)
  4190.             );
  4191.         }
  4192.     },
  4193.         
  4194.     optionsInitialize: function() {
  4195.         // Save the reference to the Autofill Forms preferences branch:
  4196.         this.autofillFormsPrefs = this.getPrefManager().getBranch('extensions.autofillForms@blueimp.net.');
  4197.         
  4198.         // Initialize the profile lists:
  4199.         this.initProfilesLists();
  4200.         // Initialize the fieldRules tree:
  4201.         this.initTree();
  4202.         // Initialize the simple interface:
  4203.         this.initSimpleInterface();
  4204.  
  4205.         // Sort is to be ascending if clicked first:
  4206.         this.ascending = true;
  4207.         this.profilesAscending = true;
  4208.  
  4209.         var configDirectoryTextbox = document.getElementById('configDirectoryTextBox');
  4210.         if(configDirectoryTextbox && this.autofillFormsPrefs.prefHasUserValue('configDirectory')) {
  4211.             configDirectory = this.getConfigDirectory();
  4212.             if(configDirectory) {
  4213.                 configDirectoryTextbox.value = configDirectory.path;
  4214.             }
  4215.         }
  4216.         
  4217.         // Initialize the keyboard shortcut objects:
  4218.         this.shortcut = new Object();
  4219.         this.shortcut['shortcut'] = null;
  4220.         this.shortcut['shortcutSubmit'] = null;
  4221.         this.shortcut['shortcutAllTabs'] = null;
  4222.         this.shortcut['shortcutFromProfileSelection'] = null;
  4223.         this.shortcut['shortcutProfile'] = null;
  4224.         this.shortcut['shortcutSettings'] = null;
  4225.         this.shortcut['shortcutDisplayFormDetails'] = null;
  4226.  
  4227.         // Display the shortcut combinations:
  4228.         for(var property in this.shortcut) {
  4229.             if(document.getElementById(property)) {
  4230.                 document.getElementById(property).value = this.getFormattedShortcut(this.getShortcut(property));
  4231.             }
  4232.         }
  4233.         
  4234.         // Initialize the mouse button objects:
  4235.         this.mouseButton = new Object();
  4236.         this.mouseButton['mouseShortcut'] = null;
  4237.         this.mouseButton['mouseShortcutSubmit'] = null;
  4238.         this.mouseButton['mouseShortcutAllTabs'] = null;
  4239.         this.mouseButton['mouseShortcutFromProfileSelection'] = null;
  4240.         this.mouseButton['mouseShortcutProfile'] = null;
  4241.         this.mouseButton['mouseShortcutSettings'] = null;
  4242.         this.mouseButton['mouseShortcutDisplayFormDetails'] = null;
  4243.  
  4244.         // Display the mouse button combinations:
  4245.         for(var property in this.mouseButton) {
  4246.             if(document.getElementById(property)) {
  4247.                 document.getElementById(property).value = this.getFormattedMouseButton(this.getMouseButton(property));
  4248.             }
  4249.         }
  4250.         
  4251.         // Parse the window params (e.g. initializing the target form field values):
  4252.         this.parseOptionsWindowParams();
  4253.     },
  4254.  
  4255.     initSimpleInterface: function() {
  4256.         var rows = document.getElementById('simpleInterfaceRows');
  4257.         if(rows) {
  4258.             while(rows.hasChildNodes()) {
  4259.                 rows.removeChild(rows.firstChild);
  4260.             }
  4261.             for(var i=0; i<this.getFieldRules().length; i++) {
  4262.                 // Only show enabled fieldRules:
  4263.                 if(!this.getFieldRules()[i]['fieldRuleEnabled']) {
  4264.                     continue;
  4265.                 }
  4266.                 rows.appendChild(
  4267.                     this.getSimpleInterfaceRow(
  4268.                         i,
  4269.                         this.getFieldRules()[i]['fieldRuleName'],
  4270.                         this.getFieldRules()[i]['fieldRuleValue']
  4271.                     )
  4272.                 );
  4273.             }    
  4274.         }
  4275.     },
  4276.  
  4277.     addSimpleInterfaceRow: function(index) {
  4278.         var row = this.getSimpleInterfaceRow(
  4279.                         index,
  4280.                         this.getFieldRules()[index]['fieldRuleName'],
  4281.                         this.getFieldRules()[index]['fieldRuleValue']
  4282.                     )
  4283.         var rows = document.getElementById('simpleInterfaceRows');
  4284.         if(rows) {
  4285.             var nextSibling;
  4286.             if(rows.childNodes) {
  4287.                 for(var i=index+1; i<rows.childNodes.length; i++) {
  4288.                     nextSibling = document.getElementById('simpleInterfaceRow_'+i);
  4289.                     if(nextSibling) {
  4290.                         rows.insertBefore(row, nextSibling);
  4291.                         break;
  4292.                     }
  4293.                 }
  4294.             }
  4295.             if(!nextSibling) {
  4296.                 rows.appendChild(row);
  4297.             }
  4298.         }
  4299.     },
  4300.     
  4301.     removeSimpleInterfaceRow: function(index) {
  4302.         var row = document.getElementById('simpleInterfaceRow_'+index);
  4303.         if(row) {
  4304.             row.parentNode.removeChild(row);
  4305.         }
  4306.     },
  4307.     
  4308.     getSimpleInterfaceRow: function(index, name, value) {
  4309.         if(!arguments.callee.row) {
  4310.             arguments.callee.row = document.createElement('row');
  4311.             arguments.callee.row.setAttribute('align', 'center');
  4312.             var label = document.createElement('label');
  4313.             var textbox = document.createElement('textbox');
  4314.             textbox.setAttribute(
  4315.                 'onchange',
  4316.                 'autofillForms.applySimpleInterfaceValue(this)'
  4317.             );
  4318.             textbox.setAttribute(
  4319.                 'oninput',
  4320.                 'this.value=autofillForms.replaceControlCharacters(this.value)'
  4321.             );
  4322.             textbox.setAttribute('newlines', 'pasteintact');
  4323.             arguments.callee.row.appendChild(label);
  4324.             arguments.callee.row.appendChild(textbox);    
  4325.         }
  4326.         var row = arguments.callee.row.cloneNode(true);
  4327.         row.setAttribute('id', 'simpleInterfaceRow_'+index);
  4328.         row.firstChild.setAttribute('value', name+':');
  4329.         row.firstChild.setAttribute('id', 'simpleInterfaceLabel_'+index);
  4330.         row.firstChild.setAttribute('control', 'simpleInterfaceTextbox_'+index);
  4331.         row.lastChild.setAttribute('id', 'simpleInterfaceTextbox_'+index);
  4332.         row.lastChild.setAttribute('value', value);
  4333.         // Is textbox a password field?
  4334.         if(this.getRegExpPasswordLabel().test(name)) {
  4335.             row.lastChild.setAttribute('type', 'password');
  4336.         }
  4337.         return row;
  4338.     },
  4339.     
  4340.     applySimpleInterfaceValue: function(textBox) {
  4341.         // See method selectedFieldRule() why this has to be set to null:
  4342.         this.lastSelectedIndex = null;
  4343.         
  4344.         var index = parseInt(textBox.getAttribute('id').split('_')[1]);
  4345.         this.getFieldRules()[index]['fieldRuleValue'] = this.makeSafe(textBox.value);
  4346.  
  4347.         // Notify the tree (of the advanced interface):
  4348.         try {
  4349.             this.treeBox.invalidateRow(index);
  4350.         } catch(e) {
  4351.         }
  4352.             
  4353.         // Update the preferences:
  4354.         this.setFieldRules();    
  4355.     },
  4356.     
  4357.     updateSimpleInterfaceRow: function(index) {
  4358.         var row = document.getElementById('simpleInterfaceRow_'+index);
  4359.         if(row) {
  4360.             row.firstChild.value = this.getFieldRules()[index]['fieldRuleName']+':';
  4361.             row.lastChild.value = this.getFieldRules()[index]['fieldRuleValue'];
  4362.             // Is textbox a password field?
  4363.             if(this.getRegExpPasswordLabel().test(this.getFieldRules()[index]['fieldRuleName'])) {
  4364.                 row.lastChild.setAttribute('type', 'password');
  4365.             } else {
  4366.                 row.lastChild.removeAttribute('type');
  4367.             }
  4368.         }
  4369.     },
  4370.  
  4371.     getFieldRuleNameForElement: function(element) {
  4372.         // Use the form field label as name if available:
  4373.         var labelValue = this.getLabelForElement(element);
  4374.         // Remove the colon, if present:
  4375.         if(labelValue && labelValue.charAt(labelValue.length-1) == ':') {
  4376.             labelValue = labelValue.substr(0, labelValue.length-1);
  4377.         }
  4378.         // If no label could be found, use the name or the id with the first character in upper case:
  4379.         if(!labelValue) {
  4380.             labelValue = element.name;
  4381.             if(!labelValue) {
  4382.                 labelValue = element.id;
  4383.             }
  4384.             if(labelValue) {
  4385.                 labelValue = labelValue.charAt(0).toUpperCase() + labelValue.substr(1);
  4386.             }
  4387.         }
  4388.         return labelValue;
  4389.     },
  4390.     
  4391.     getRegExpStrForValue: function(value) {
  4392.         try {
  4393.             // Remove unsave characters, escape regexp characters and return the regexp string:
  4394.             return this.getRegExpStr('(?:^'+this.escapeRegExp(this.makeSafe(value))+'$)');
  4395.         } catch(e) {
  4396.             // If an error occurs, return the safe value string.
  4397.             // If using it as regular expression fails a simple string comparison is used:
  4398.             return this.makeSafe(value);
  4399.         }
  4400.     },
  4401.     
  4402.     getFieldRuleForElement: function(element) {
  4403.         var name = element.name;
  4404.         // If no name is available use the label as fallback:
  4405.         if(!name) {
  4406.             name = this.getLabelForElement(element);
  4407.         }
  4408.         // If no name and no label is available use the id as fallback:
  4409.         if(!name) {
  4410.             name = element.id;
  4411.         }
  4412.         try {
  4413.             // Remove unsave characters, escape regexp characters and return the regexp string:
  4414.             return this.getRegExpStr('(?:^'+this.escapeRegExp(this.makeSafe(name))+'$)');
  4415.         } catch(e) {
  4416.             // If an error occurs, return an always matching regexp string:
  4417.             return '(?:)';
  4418.         }
  4419.     },
  4420.     
  4421.     getSiteRuleForURL: function(url) {
  4422.         try {
  4423.             // Remove unsave characters, escape regexp characters and return the regexp string:
  4424.             return this.getRegExpStr('(?:^'+this.escapeRegExp(this.makeSafe(url))+')');
  4425.         } catch(e) {
  4426.             // If an error occurs, return an always matching regexp string:
  4427.             return '(?:)';
  4428.         }
  4429.     },
  4430.     
  4431.     parseOptionsWindowParams: function() {
  4432.         // Check the windows arguments:
  4433.         if(window.arguments && window.arguments[0]) {
  4434.             if(window.arguments[0].targetFormField) {
  4435.                 var formFieldObject = window.arguments[0].targetFormField;
  4436.                 
  4437.                 var value;
  4438.                 switch(formFieldObject.type) {
  4439.                     case 'checkbox':
  4440.                     case 'radio':
  4441.                     case 'select-one':
  4442.                     case 'select-multiple':
  4443.                         value = this.getRegExpStrForValue(formFieldObject.value);
  4444.                         break;
  4445.                     default:
  4446.                         value = this.replaceControlCharacters(formFieldObject.value);
  4447.                         break;
  4448.                 }
  4449.                 
  4450.                 var location = this.getDoc().location;
  4451.                 
  4452.                 // Reset the targetFormField of the autofillForms object referenced by window.arguments[0]:
  4453.                 window.arguments[0].targetFormField = null;
  4454.                             
  4455.                 // Set the textbox values using the form field properties and the current document location:
  4456.                 document.getElementById('fieldRuleNameTextBox').value
  4457.                     = this.getFieldRuleNameForElement(formFieldObject)
  4458.                     + (location.hostname ? ' - ' + location.hostname : '');
  4459.                 document.getElementById('fieldRuleValueTextBox').value = value;
  4460.                 document.getElementById('fieldRuleFieldRuleTextBox').value
  4461.                     = this.getFieldRuleForElement(formFieldObject);
  4462.                 document.getElementById('fieldRuleSiteRuleTextBox').value
  4463.                     = this.getSiteRuleForURL(location.protocol + '//' + location.host);
  4464.                 
  4465.                 // Make sure the main pane is selected:
  4466.                 document.getElementById('autofillFormsPrefs').showPane(
  4467.                     document.getElementById('autofillFormsPrefPaneMain')
  4468.                 );
  4469.                 
  4470.                 // Set the focus to the name field:
  4471.                 document.getElementById('fieldRuleNameTextBox').focus();
  4472.             } else if(window.arguments[0].newProfileFromForm) {
  4473.                 // Make sure the main pane is selected:
  4474.                 document.getElementById('autofillFormsPrefs').showPane(
  4475.                     document.getElementById('autofillFormsPrefPaneMain')
  4476.                 );
  4477.             }
  4478.         }
  4479.     },
  4480.     
  4481.     optionsFinalize: function() {
  4482.     },
  4483.     
  4484.     showProfileSwitcher: function() {
  4485.         if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
  4486.             // Always retrieve the profile labels from file if useConfigDirectory is enabled:
  4487.             this.profileLabels = null;
  4488.         }
  4489.         // The nsIPromptService select() method doesn't offer to set a preselection,
  4490.         // so we switch the current profile label with the first item (which is selected by default):
  4491.         var list;
  4492.         var currentIndex = this.getProfileIndex();
  4493.         if(currentIndex != 0) {
  4494.             // Copy the profilLabels array (so we don't change the original):
  4495.             list = new Array().concat(this.getProfileLabels());
  4496.             // Switch the current profile label with the first item:
  4497.             var tmp = list[0];
  4498.             list[0] = list[currentIndex];
  4499.             list[currentIndex] = tmp;
  4500.         } else {
  4501.             // Set the list to the profilLabels reference if it is not to be changed:
  4502.             list = this.getProfileLabels();
  4503.         }
  4504.         var selected = {};
  4505.         // Show the selection prompt:
  4506.         var ok = this.getPrompts().select(
  4507.             window,
  4508.             this.getStringBundle().getString('profileSelectionWindowTitle'),
  4509.             this.getStringBundle().getString('profileSelectionPrompt'),
  4510.             list.length,
  4511.             list,
  4512.             selected
  4513.         );
  4514.         if(ok) {
  4515.             // If nothing changed, return:
  4516.             if(selected.value == 0)
  4517.                 return;
  4518.             // If the currentIndex has been selected and is not 0, it is in fact the index 0:
  4519.             if(currentIndex != 0 && selected.value == currentIndex)
  4520.                 selected.value = 0;
  4521.             // Set the profile index to the selected one:
  4522.             this.setProfileIndex(selected.value)
  4523.         }
  4524.     },
  4525.  
  4526.     showDialog: function(url, params) {
  4527.         var paramObject = params ? params : this;
  4528.         return window.openDialog(
  4529.             url,
  4530.             '',
  4531.             'chrome=yes,resizable=yes,toolbar=yes,centerscreen=yes,modal=no,dependent=no,dialog=no',
  4532.             paramObject
  4533.         );
  4534.     },
  4535.  
  4536.     inArray: function(array, item) {
  4537.         var i = array.length;
  4538.         while(i--)
  4539.             if(array[i] === item)
  4540.                 return true;
  4541.         return false;
  4542.     },
  4543.     
  4544.     displayFormDetails: function() {
  4545.         this.searchAndDisplayFormDetails(this.getWin());
  4546.     },
  4547.  
  4548.     searchAndDisplayFormDetails: function(win) {
  4549.         win = win ? win : this.getWin();
  4550.         
  4551.         var doc = this.getDoc(win);
  4552.         
  4553.         // Check if any web forms are available on the current window:
  4554.         if(doc && doc.forms && doc.forms.length > 0) {
  4555.         
  4556.              // Go through the forms:
  4557.              for(var i = 0; i < doc.forms.length; i++) {
  4558.  
  4559.                 // The form elements list:
  4560.                 var elements = doc.forms[i].elements;
  4561.                 
  4562.                 // Go through the form elements:
  4563.                 for(var j = 0; j < elements.length; j++) {
  4564.                     // Only display valid form fields:                        
  4565.                     if(this.isValidFormField(elements[j])) {
  4566.                         this.displayFormElementDetails(elements[j], j, i, doc);
  4567.                     }
  4568.                 }
  4569.             }
  4570.         }
  4571.  
  4572.         // Recursive call for all subframes:
  4573.         for(var f=0; f < win.frames.length; f++) {
  4574.             this.searchAndDisplayFormDetails(win.frames[f]);
  4575.         }
  4576.     },
  4577.  
  4578.     isValidFormField: function(element) {
  4579.         if(element.disabled) {
  4580.             return false;
  4581.         }
  4582.         if(!arguments.callee.regExpFormFieldType) {
  4583.             arguments.callee.regExpFormFieldType = new RegExp(
  4584.                 this.autofillFormsPrefs.getCharPref('regExpFormFieldTypes')
  4585.             );
  4586.         }
  4587.         return arguments.callee.regExpFormFieldType.test(element.type);
  4588.     },
  4589.     
  4590.     displayFormElementDetails: function(element, elementNumber, formNumber, doc) {
  4591.         // Create a unique id for the form element:
  4592.         var id = 'autofillForms-f' + formNumber + '-e' + elementNumber;
  4593.         
  4594.         // Remove the form details node if already present
  4595.         // (nodeType 1 is an element node):
  4596.         if(element.nextSibling && element.nextSibling.nodeType == 1 && element.nextSibling.getAttribute('id') == id) {
  4597.             element.parentNode.removeChild(element.nextSibling);
  4598.             return;
  4599.         }
  4600.         
  4601.         // Create a "span" node with element details:
  4602.         var text;
  4603.         // Display the element name if available, else the element id if available, else an empty name:
  4604.         if(element.name || !element.id) {
  4605.             text = 'name="' + element.name;
  4606.         } else {
  4607.             text = 'id="' + element.id;
  4608.         }
  4609.         // Display the element value:
  4610.         text += '" value="' +  element.value + '"';
  4611.         var span = doc.createElement('span');
  4612.         span.setAttribute('id', id);
  4613.         span.setAttribute('style', this.autofillFormsPrefs.getCharPref('formDetailsStyle'));
  4614.         span.setAttribute('title', text);
  4615.         span.appendChild(doc.createTextNode(text));
  4616.         
  4617.         // Insert the form details node after the element:
  4618.         if(element.nextSibling)
  4619.             element.parentNode.insertBefore(span, element.nextSibling);
  4620.         else
  4621.             element.parentNode.appendChild(span);
  4622.     },
  4623.     
  4624.     ruleEditorInitialize: function() {
  4625.         // Save the reference to the Autofill Forms preferences branch:
  4626.         this.autofillFormsPrefs = this.getPrefManager().getBranch('extensions.autofillForms@blueimp.net.');
  4627.         
  4628.         if(window.arguments && window.arguments[0] && window.arguments[0].attributes) {
  4629.             this.currentRuleField = window.arguments[0];
  4630.         }
  4631.         
  4632.         // Initialize the ruleElementTypes:
  4633.         this.ruleElementTypes = new Array();
  4634.         this.ruleElementTypes.push('contains');
  4635.         this.ruleElementTypes.push('beginsWith');
  4636.         this.ruleElementTypes.push('endsWith');
  4637.         this.ruleElementTypes.push('equals');
  4638.         
  4639.         // If the rule editor is used to edit the site rule, add two predefined protocol rules:
  4640.         if(this.currentRuleField && this.currentRuleField.id && this.currentRuleField.id.indexOf('SiteRule') != -1) {
  4641.             this.ruleEditorAdd('beginsWith', 'http:\/\/');
  4642.             this.ruleEditorAdd('beginsWith', 'https:\/\/');
  4643.         } else {
  4644.             this.ruleEditorAdd();
  4645.         }
  4646.     },
  4647.     
  4648.     ruleEditorSave: function() {
  4649.         if(document.getElementById('ruleElementsList')) {
  4650.             var str = '';
  4651.             var richlistbox = document.getElementById('ruleElementsList');
  4652.             var richlistitems = richlistbox.getElementsByTagName('richlistitem');
  4653.             var menulists,textboxes;
  4654.             
  4655.             // Go through the richlistbox items:
  4656.             for(var i=0; i<richlistitems.length; i++) {
  4657.                 // Link the conditions as disjunctions (OR-Relations);
  4658.                 if(str.length != 0)
  4659.                     str += '|';
  4660.                 menulists = richlistitems[i].getElementsByTagName('menulist');
  4661.                 textboxes = richlistitems[i].getElementsByTagName('textbox');
  4662.                 
  4663.                 // Add the current condition to the string:
  4664.                 switch(menulists[0].selectedItem.value) {
  4665.                     case 'contains':
  4666.                         str += '(?:' + textboxes[0].value + ')';
  4667.                         break;
  4668.                     case 'beginsWith':
  4669.                         str += '(?:^' + textboxes[0].value + ')';
  4670.                         break;
  4671.                     case 'endsWith':
  4672.                         str += '(?:' + textboxes[0].value + '$)';
  4673.                         break;
  4674.                     case 'equals':
  4675.                         str += '(?:^' + textboxes[0].value + '$)';
  4676.                         break;
  4677.                 }
  4678.             }
  4679.             if(this.currentRuleField) {
  4680.                 // Set the current field value to the created string:
  4681.                 this.currentRuleField.value = str;
  4682.                 // Call the onchange handler:
  4683.                 if(this.currentRuleField.onchange) {
  4684.                     this.currentRuleField.onchange();
  4685.                 }
  4686.                 // Call the focus handler:
  4687.                 if(this.currentRuleField.focus) {
  4688.                     this.currentRuleField.focus();
  4689.                 }
  4690.             }
  4691.         }
  4692.         return true;
  4693.     },
  4694.     
  4695.     ruleEditorAdd: function(type, ruleElement) {
  4696.         if(document.getElementById('ruleElementsList')) {
  4697.             var richlistbox = document.getElementById('ruleElementsList');
  4698.             
  4699.             var richlistitem,menulist,menupopup,menuitem,textbox,label;
  4700.             
  4701.             richlistitem = document.createElement('richlistitem');
  4702.             
  4703.             // Create the condition type menu:
  4704.             menulist = document.createElement('menulist');
  4705.             menupopup = document.createElement('menupopup');
  4706.             
  4707.             var selectedIndex = 0;
  4708.             
  4709.             // Create the menu of ruleElementTypes:
  4710.             for(var i=0; i<this.ruleElementTypes.length; i++) {
  4711.                 menuitem = document.createElement('menuitem');
  4712.                 menuitem.setAttribute(
  4713.                     'value',
  4714.                     this.ruleElementTypes[i]
  4715.                 );
  4716.                 menuitem.setAttribute(
  4717.                     'label',
  4718.                     this.getStringBundle().getString(this.ruleElementTypes[i] + 'RuleType')
  4719.                 );
  4720.                 menupopup.appendChild(menuitem);
  4721.                 
  4722.                 // Set the selectedIndex:
  4723.                 if(type != null && type == this.ruleElementTypes[i])
  4724.                     selectedIndex = i;
  4725.             }
  4726.             
  4727.             menulist.appendChild(menupopup);
  4728.             richlistitem.appendChild(menulist);
  4729.             
  4730.             // Create the textbox:        
  4731.             textbox = document.createElement('textbox');
  4732.             if(ruleElement != null)
  4733.                 textbox.setAttribute('value',ruleElement);
  4734.             textbox.setAttribute('flex','1');
  4735.             richlistitem.appendChild(textbox);
  4736.  
  4737.             richlistbox.appendChild(richlistitem);
  4738.             
  4739.             // Select the menuitem:
  4740.             menulist.selectedIndex = selectedIndex;
  4741.         }
  4742.     },
  4743.     
  4744.     ruleEditorRemove: function(index) {
  4745.         var ruleElementsList = document.getElementById('ruleElementsList');
  4746.         if(ruleElementsList) {
  4747.             if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
  4748.                 // Confirmation dialog:
  4749.                 if(!this.getPrompts().confirm(
  4750.                         null,
  4751.                         this.getStringBundle().getString('removeRuleConditionTitle'),
  4752.                         this.getStringBundle().getString('removeRuleConditionText')
  4753.                     )
  4754.                 ) {
  4755.                     return;    
  4756.                 }
  4757.             }
  4758.         
  4759.             var richlistbox = ruleElementsList;
  4760.             if(index)
  4761.                 richlistbox.selectedIndex = index;
  4762.             if(richlistbox.selectedItem && richlistbox.selectedIndex != -1)
  4763.                 richlistbox.removeChild(richlistbox.selectedItem);
  4764.         }
  4765.     },
  4766.  
  4767.     ruleEditorIsTextBoxFocused: function() {
  4768.         return this.ruleEditorTextBoxFocused;
  4769.     },
  4770.     
  4771.     ruleEditorFocus: function() {
  4772.         var focusedElement = document.commandDispatcher.focusedElement;
  4773.         
  4774.         // Monitor if a textbox is focused:
  4775.         if(!this.ruleEditorTextBoxFocused && focusedElement && focusedElement.tagName == 'html:input') {
  4776.             this.ruleEditorTextBoxFocused = true;
  4777.         } else if(this.ruleEditorTextBoxFocused && focusedElement && focusedElement.tagName == 'richlistbox') {
  4778.             this.ruleEditorTextBoxFocused = false;
  4779.         }
  4780.     },
  4781.     
  4782.     ruleEditorHandleKeyPress: function(event) {
  4783.         // Only remove a dynamic tag on delete key press if no textbox is focused:
  4784.         if(event.keyCode == 46 && !this.ruleEditorIsTextBoxFocused()) {
  4785.             this.ruleEditorRemove();
  4786.         }
  4787.     },
  4788.  
  4789.     ruleEditorFinalize: function() {
  4790.         this.currentRuleField = null;
  4791.     },
  4792.  
  4793.     tagEditorInitialize: function() {
  4794.         // Save the reference to the Autofill Forms preferences branch:
  4795.         this.autofillFormsPrefs = this.getPrefManager().getBranch('extensions.autofillForms@blueimp.net.');
  4796.         
  4797.         // Add existing tags to the list:
  4798.         for(var i=0; i<this.getDynamicTags().length; i++) {
  4799.             // Catch if the number of tags doesn't match the number of tag codes:
  4800.             try {
  4801.                 this.tagEditorAdd(this.getDynamicTags()[i],this.getDynamicTagCodes()[i])
  4802.             } catch(e) {
  4803.                 this.log(e);
  4804.             }
  4805.         }
  4806.     },
  4807.     
  4808.     tagEditorSave: function() {
  4809.         var richlistbox = document.getElementById('tagList');
  4810.         if(richlistbox) {
  4811.             var richlistitems = richlistbox.getElementsByTagName('richlistitem');
  4812.             var textboxes;
  4813.             
  4814.             var dynamicTags = new Array();
  4815.             var dynamicTagCodes = new Array();
  4816.             
  4817.             // Go through the richlistbox items:
  4818.             for(var i=0; i<richlistitems.length; i++) {
  4819.                 textboxes = richlistitems[i].getElementsByTagName('textbox');
  4820.                 
  4821.                 // Add the dynamic tags and their associated tag codes to the lists:
  4822.                 dynamicTags.push(this.makeSafe(textboxes[0].value));
  4823.                 dynamicTagCodes.push(this.makeSafe(textboxes[1].value));
  4824.             }
  4825.             
  4826.             // Save the lists in the preferences:
  4827.             this.setDynamicTags(dynamicTags);
  4828.             this.setDynamicTagCodes(dynamicTagCodes);
  4829.         }
  4830.         return true;
  4831.     },
  4832.     
  4833.     tagEditorAdd: function(tag, tagCode) {
  4834.         var richlistbox = document.getElementById('tagList');
  4835.         if(richlistbox) {            
  4836.             var richlistitem,textbox;
  4837.             
  4838.             richlistitem = document.createElement('richlistitem');
  4839.  
  4840.             // Create the tag textbox:        
  4841.             textbox = document.createElement('textbox');
  4842.             textbox.setAttribute('class','tag');
  4843.             if(tag != null)
  4844.                 textbox.setAttribute('value',tag);
  4845.             richlistitem.appendChild(textbox);
  4846.  
  4847.             // Create the tagCode textbox:        
  4848.             textbox = document.createElement('textbox');
  4849.             textbox.setAttribute('class','tagCode');
  4850.             textbox.setAttribute('flex','1');
  4851.             if(tagCode != null)
  4852.                 textbox.setAttribute('value',tagCode);
  4853.             richlistitem.appendChild(textbox);
  4854.             
  4855.             richlistbox.appendChild(richlistitem);
  4856.         }
  4857.     },
  4858.     
  4859.     tagEditorRemove: function(index) {
  4860.         var richlistbox = document.getElementById('tagList');
  4861.         if(richlistbox) {
  4862.             if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
  4863.                 // Confirmation dialog:
  4864.                 if(!this.getPrompts().confirm(
  4865.                         null,
  4866.                         this.getStringBundle().getString('removeDynamicTagTitle'),
  4867.                         this.getStringBundle().getString('removeDynamicTagText')
  4868.                     )
  4869.                 ) {
  4870.                     return;    
  4871.                 }
  4872.             }
  4873.             if(index)
  4874.                 richlistbox.selectedIndex = index;
  4875.             if(richlistbox.selectedItem && richlistbox.selectedIndex != -1)
  4876.                 richlistbox.removeChild(richlistbox.selectedItem);
  4877.         }
  4878.     },
  4879.  
  4880.     tagEditorValidate: function(index) {
  4881.         var richlistbox = document.getElementById('tagList');
  4882.         if(richlistbox) {
  4883.             if(index)
  4884.                 richlistbox.selectedIndex = index;
  4885.             if(richlistbox.selectedItem) {
  4886.                 var tagCode = richlistbox.selectedItem.lastChild.value;
  4887.                 var validationResultTextBox = document.getElementById('validationResultTextBox');
  4888.                 try {
  4889.                     validationResultTextBox.removeAttribute('style');
  4890.                     // We use eval() here without restrictions - the given tagCode must be trusted:
  4891.                     validationResultTextBox.value = eval(tagCode);
  4892.                 } catch(e) {
  4893.                     validationResultTextBox.setAttribute('style', 'color:red;');
  4894.                     validationResultTextBox.value = e;
  4895.                 }
  4896.             }
  4897.         }
  4898.     },
  4899.  
  4900.     tagEditorIsTextBoxFocused: function() {
  4901.         return this.tagEditorTextBoxFocused;
  4902.     },
  4903.     
  4904.     tagEditorFocus: function() {
  4905.         var focusedElement = document.commandDispatcher.focusedElement;
  4906.         
  4907.         // Monitor if a textbox is focused:
  4908.         if(!this.tagEditorTextBoxFocused && focusedElement && focusedElement.tagName == 'html:input') {
  4909.             this.tagEditorTextBoxFocused = true;
  4910.         } else if(this.tagEditorTextBoxFocused && focusedElement && focusedElement.tagName == 'richlistbox') {
  4911.             this.tagEditorTextBoxFocused = false;
  4912.         }
  4913.     },
  4914.     
  4915.     tagEditorHandleKeyPress: function(event) {
  4916.         // Only remove a dynamic tag on delete key press if no textbox is focused:
  4917.         if(event.keyCode == 46 && !this.tagEditorIsTextBoxFocused()) {
  4918.             this.tagEditorRemove();
  4919.         }
  4920.     },
  4921.     
  4922.     tagEditorFinalize: function() {
  4923.     },
  4924.  
  4925.     openHelp: function(topic) {
  4926.         if(!topic) {
  4927.             topic = '';
  4928.         }
  4929.         var url = this.autofillFormsPrefs.getCharPref('helpURL').replace(/\[TOPIC\]$/, topic);
  4930.         this.openNewTab(url, true);
  4931.     },
  4932.     
  4933.     openNewTab: function(url, focus) {
  4934.         var helpTab = this.getBrowser().addTab(url);
  4935.         if(focus) {
  4936.             this.getBrowser().selectedTab = helpTab;
  4937.             this.getWindowMediator().getMostRecentWindow('navigator:browser').focus();
  4938.         }
  4939.     },
  4940.  
  4941.     addLeadingZeros: function(number, length) {
  4942.         number = number.toString();
  4943.         while(number.length < length) {
  4944.             number = '0'+number;
  4945.         }
  4946.         return number;
  4947.     },
  4948.     
  4949.     escapeRegExp: function(str) {
  4950.         if (!arguments.callee.regExp) {
  4951.             var specials = new Array(
  4952.                 '^', '$', '*', '+', '?', '.', '|', '/',
  4953.                 '(', ')', '[', ']', '{', '}', '\\'
  4954.             );
  4955.             arguments.callee.regExp = new RegExp(
  4956.                 '(\\' + specials.join('|\\') + ')', 'g'
  4957.             );
  4958.         }
  4959.         return str.replace(arguments.callee.regExp, '\\$1');
  4960.     },
  4961.     
  4962.     getClipboardText: function() {
  4963.         var clipboardText = null;
  4964.         var clip  = Components.classes['@mozilla.org/widget/clipboard;1']
  4965.                         .getService(Components.interfaces.nsIClipboard);
  4966.         if(!clip) {
  4967.             return null;
  4968.         }
  4969.         
  4970.         var trans = Components.classes['@mozilla.org/widget/transferable;1']
  4971.                         .createInstance(Components.interfaces.nsITransferable);
  4972.         if(!trans) {
  4973.             return null;
  4974.         }
  4975.         
  4976.         trans.addDataFlavor('text/unicode');
  4977.         
  4978.         clip.getData(trans, clip.kGlobalClipboard);
  4979.         
  4980.         var str       = new Object();
  4981.         var strLength = new Object();
  4982.         
  4983.         trans.getTransferData('text/unicode', str, strLength);
  4984.         
  4985.         if(str) {
  4986.             str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
  4987.         }
  4988.         if(str) {
  4989.             clipboardText = str.data.substring(0, strLength.value / 2);
  4990.         }
  4991.         
  4992.         return clipboardText
  4993.     },
  4994.  
  4995.     getMasterSecurityDevice: function() {
  4996.         return Components.classes['@mozilla.org/security/pk11tokendb;1']
  4997.                 .getService(Components.interfaces.nsIPK11TokenDB);
  4998.     },
  4999.     
  5000.     log: function(aMessage, aSourceName, aSourceLine, aLineNumber, aColumnNumber, aFlags, aCategory) {
  5001.         var consoleService = Components.classes['@mozilla.org/consoleservice;1']
  5002.             .getService(Components.interfaces.nsIConsoleService);
  5003.         if(aSourceName != 'undefined') {
  5004.             var scriptError = Components.classes["@mozilla.org/scripterror;1"]
  5005.                 .createInstance(Components.interfaces.nsIScriptError);
  5006.             scriptError.init(
  5007.                 aMessage,
  5008.                 aSourceName,
  5009.                 aSourceLine,
  5010.                 aLineNumber,
  5011.                 aColumnNumber,
  5012.                 aFlags,
  5013.                 aCategory
  5014.             );
  5015.             consoleService.logMessage(scriptError);
  5016.         } else {
  5017.             consoleService.logStringMessage(aMessage);    
  5018.         }
  5019.     },
  5020.  
  5021.     finalizeToolbarButtonStatus: function() {
  5022.         var autofillFormsButton = document.getElementById('autofillFormsButton');
  5023.         var hideToolbarButton = this.autofillFormsPrefs.getBoolPref('hideToolbarButton');
  5024.         if(!autofillFormsButton && !hideToolbarButton) {
  5025.             // If the toolbar button icon has been removed from the toolbar by drag&drop
  5026.             // enable the hideToolbarButton setting:
  5027.             this.autofillFormsPrefs.setBoolPref('hideToolbarButton', true);
  5028.         } else if(autofillFormsButton && !autofillFormsButton.getAttribute('hidden')) {
  5029.             // If the toolbar button icon has been added to the toolbar by drag&drop
  5030.             // disable the hideToolbarButton setting:
  5031.             this.autofillFormsPrefs.setBoolPref('hideToolbarButton', false);
  5032.         }
  5033.     },
  5034.     
  5035.     finalize: function() {
  5036.         this.finalizeToolbarButtonStatus();
  5037.         
  5038.         // Remove the content area context menu listener:
  5039.         var contentAreaContextMenu = document.getElementById('contentAreaContextMenu');
  5040.         if(contentAreaContextMenu) {
  5041.             contentAreaContextMenu.removeEventListener(
  5042.                 'popupshowing',
  5043.                 this.contentAreaContextMenuEventListener,
  5044.                 false
  5045.             );
  5046.         }
  5047.         
  5048.         // Remove the preferences Observer:
  5049.         this.autofillFormsPrefs.removeObserver('', this);
  5050.     }
  5051.     
  5052. }